foss-design 0.5.0 → 0.6.1
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 +544 -361
- package/dist/runtime/index.js +121 -6
- package/dist/viewer-lib/types/src/shared/types.d.ts +7 -0
- package/package.json +1 -1
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 path26 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 fs12 from "fs";
|
|
138
|
+
import path12 from "path";
|
|
139
139
|
|
|
140
140
|
// src/build/static.ts
|
|
141
|
-
import
|
|
142
|
-
import
|
|
141
|
+
import fs11 from "fs";
|
|
142
|
+
import path11 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 fs7 from "fs";
|
|
311
|
+
import path7 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(path27) {
|
|
350
|
+
return path27.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, path27) {
|
|
370
|
+
const parts = split(path27.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, path26) {
|
|
|
385
385
|
};
|
|
386
386
|
return walk2(0, 0);
|
|
387
387
|
}
|
|
388
|
-
function matchRoute(entries,
|
|
388
|
+
function matchRoute(entries, path27) {
|
|
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, path27);
|
|
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 folder served at the root of every screen, like Vite's `public`, relative to the project root. */
|
|
430
|
+
public: z.string().min(1).optional(),
|
|
429
431
|
port: z.number().int().min(1).max(65535).optional()
|
|
430
432
|
});
|
|
431
433
|
var SystemConfigSchema = z.strictObject({
|
|
@@ -510,9 +512,9 @@ var CanvasSchema = z.strictObject({
|
|
|
510
512
|
pages: z.array(z.unknown()).min(1, "add at least one page")
|
|
511
513
|
});
|
|
512
514
|
var ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/i;
|
|
513
|
-
function formatPath(base,
|
|
515
|
+
function formatPath(base, path27) {
|
|
514
516
|
let out = base;
|
|
515
|
-
for (const part of
|
|
517
|
+
for (const part of path27) out += typeof part === "number" ? `[${part}]` : out ? `.${String(part)}` : String(part);
|
|
516
518
|
return out;
|
|
517
519
|
}
|
|
518
520
|
function describeZodError(error, base = "") {
|
|
@@ -749,7 +751,7 @@ function loadCanvas(paths, canvasId, urls, snapshots) {
|
|
|
749
751
|
};
|
|
750
752
|
doc.pages.push(out);
|
|
751
753
|
});
|
|
752
|
-
checkNavigation(doc, screens, issues, fileRel, (file2) => relToRoot(paths, file2));
|
|
754
|
+
checkNavigation(doc, canvasCodeFiles(dir, screens), issues, fileRel, (file2) => relToRoot(paths, file2));
|
|
753
755
|
return { doc, screens };
|
|
754
756
|
}
|
|
755
757
|
var GO_IMPORT = /import\s*\{[^}]*\bgo\b[^}]*\}\s*from\s*['"]@design\/runtime['"]/;
|
|
@@ -768,7 +770,27 @@ function legacyGoLines(file) {
|
|
|
768
770
|
const calls = [...source.matchAll(GO_USE)].map((match) => lineOf(match.index)).filter((line) => line !== lineOf(imported.index));
|
|
769
771
|
return calls.length ? [...new Set(calls)] : [lineOf(imported.index)];
|
|
770
772
|
}
|
|
771
|
-
|
|
773
|
+
var CODE_FILE = /\.(?:[cm]?[jt]sx?|html?)$/i;
|
|
774
|
+
function canvasCodeFiles(dir, screens) {
|
|
775
|
+
const files = new Set(screens.map((screen) => screen.file));
|
|
776
|
+
const visit = (current) => {
|
|
777
|
+
let entries;
|
|
778
|
+
try {
|
|
779
|
+
entries = fs3.readdirSync(current, { withFileTypes: true });
|
|
780
|
+
} catch {
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
for (const entry of entries) {
|
|
784
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
785
|
+
const full = path3.join(current, entry.name);
|
|
786
|
+
if (entry.isDirectory()) visit(full);
|
|
787
|
+
else if (entry.isFile() && CODE_FILE.test(entry.name)) files.add(full);
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
visit(dir);
|
|
791
|
+
return [...files];
|
|
792
|
+
}
|
|
793
|
+
function checkNavigation(doc, codeFiles, issues, canvasFile, rel) {
|
|
772
794
|
const frames = doc.pages.flatMap(
|
|
773
795
|
(page) => page.sections.flatMap(
|
|
774
796
|
(section) => section.items.filter((item) => item.kind === "screen" || item.kind === "url").map((item) => ({ page, item }))
|
|
@@ -787,7 +809,7 @@ function checkNavigation(doc, screens, issues, canvasFile, rel) {
|
|
|
787
809
|
});
|
|
788
810
|
}
|
|
789
811
|
}
|
|
790
|
-
for (const file of
|
|
812
|
+
for (const file of codeFiles) {
|
|
791
813
|
for (const line of legacyGoLines(file)) {
|
|
792
814
|
issues.push({ severity: "error", file: rel(file), at: `line ${line}`, message: GO_REMOVED });
|
|
793
815
|
}
|
|
@@ -809,13 +831,49 @@ function summarize(canvas) {
|
|
|
809
831
|
};
|
|
810
832
|
}
|
|
811
833
|
|
|
834
|
+
// src/core/public.ts
|
|
835
|
+
import fs4 from "fs";
|
|
836
|
+
import path4 from "path";
|
|
837
|
+
function publicFiles(dir) {
|
|
838
|
+
const files = [];
|
|
839
|
+
let root;
|
|
840
|
+
try {
|
|
841
|
+
root = fs4.realpathSync(dir);
|
|
842
|
+
} catch {
|
|
843
|
+
return files;
|
|
844
|
+
}
|
|
845
|
+
const visit = (current) => {
|
|
846
|
+
let entries;
|
|
847
|
+
try {
|
|
848
|
+
entries = fs4.readdirSync(current, { withFileTypes: true });
|
|
849
|
+
} catch {
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
for (const entry of entries) {
|
|
853
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
854
|
+
const full = path4.join(current, entry.name);
|
|
855
|
+
if (entry.isDirectory()) visit(full);
|
|
856
|
+
else if (entry.isFile()) files.push(toPosix(path4.relative(dir, full)));
|
|
857
|
+
else if (entry.isSymbolicLink()) {
|
|
858
|
+
try {
|
|
859
|
+
const target = fs4.realpathSync(full);
|
|
860
|
+
if (isInside(root, target) && fs4.statSync(target).isFile()) files.push(toPosix(path4.relative(dir, full)));
|
|
861
|
+
} catch {
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
visit(dir);
|
|
867
|
+
return files.sort();
|
|
868
|
+
}
|
|
869
|
+
|
|
812
870
|
// src/core/system.ts
|
|
813
|
-
import
|
|
814
|
-
import
|
|
871
|
+
import fs6 from "fs";
|
|
872
|
+
import path6 from "path";
|
|
815
873
|
|
|
816
874
|
// src/core/tokens.ts
|
|
817
|
-
import
|
|
818
|
-
import
|
|
875
|
+
import fs5 from "fs";
|
|
876
|
+
import path5 from "path";
|
|
819
877
|
import postcss from "postcss";
|
|
820
878
|
var NAMESPACES = [
|
|
821
879
|
["--color-", "color"],
|
|
@@ -974,12 +1032,12 @@ function readStylesheets(entry) {
|
|
|
974
1032
|
const seen = /* @__PURE__ */ new Set();
|
|
975
1033
|
const files = [];
|
|
976
1034
|
const walk2 = (file) => {
|
|
977
|
-
if (seen.has(file) || !
|
|
1035
|
+
if (seen.has(file) || !fs5.existsSync(file)) return;
|
|
978
1036
|
seen.add(file);
|
|
979
|
-
const css2 =
|
|
1037
|
+
const css2 = fs5.readFileSync(file, "utf8");
|
|
980
1038
|
const imports = [...css2.matchAll(/@import\s+(?:url\()?\s*["']([^"']+)["']/g)].map((m) => m[1]);
|
|
981
1039
|
for (const spec of imports) {
|
|
982
|
-
if (spec.startsWith("./") || spec.startsWith("../")) walk2(
|
|
1040
|
+
if (spec.startsWith("./") || spec.startsWith("../")) walk2(path5.resolve(path5.dirname(file), spec));
|
|
983
1041
|
}
|
|
984
1042
|
files.push({ file, css: css2 });
|
|
985
1043
|
};
|
|
@@ -1115,11 +1173,11 @@ var COMPONENT_EXT = [".tsx", ".jsx", ".ts", ".js", ".vue", ".svelte", ".html"];
|
|
|
1115
1173
|
var IMAGE_EXT = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif", ".svg", ".ico"]);
|
|
1116
1174
|
var FONT_EXT = /* @__PURE__ */ new Set([".woff", ".woff2", ".ttf", ".otf"]);
|
|
1117
1175
|
function listFiles(dir, deep = false) {
|
|
1118
|
-
if (!
|
|
1176
|
+
if (!fs6.existsSync(dir)) return [];
|
|
1119
1177
|
const out = [];
|
|
1120
|
-
for (const entry of
|
|
1178
|
+
for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
|
|
1121
1179
|
if (entry.name.startsWith(".")) continue;
|
|
1122
|
-
const full =
|
|
1180
|
+
const full = path6.join(dir, entry.name);
|
|
1123
1181
|
if (entry.isDirectory()) {
|
|
1124
1182
|
if (deep) out.push(...listFiles(full, true));
|
|
1125
1183
|
} else out.push(full);
|
|
@@ -1127,16 +1185,16 @@ function listFiles(dir, deep = false) {
|
|
|
1127
1185
|
return out.sort();
|
|
1128
1186
|
}
|
|
1129
1187
|
function guessSources(componentsDir, stem) {
|
|
1130
|
-
if (!
|
|
1188
|
+
if (!fs6.existsSync(componentsDir)) return [];
|
|
1131
1189
|
const wanted = slugify(stem);
|
|
1132
1190
|
for (const file of listFiles(componentsDir)) {
|
|
1133
|
-
const ext =
|
|
1134
|
-
if (COMPONENT_EXT.includes(ext) && slugify(
|
|
1191
|
+
const ext = path6.extname(file);
|
|
1192
|
+
if (COMPONENT_EXT.includes(ext) && slugify(path6.basename(file, ext)) === wanted) return [file];
|
|
1135
1193
|
}
|
|
1136
|
-
for (const entry of
|
|
1194
|
+
for (const entry of fs6.readdirSync(componentsDir, { withFileTypes: true })) {
|
|
1137
1195
|
if (entry.isDirectory() && slugify(entry.name) === wanted) {
|
|
1138
|
-
const files = listFiles(
|
|
1139
|
-
(f) => COMPONENT_EXT.includes(
|
|
1196
|
+
const files = listFiles(path6.join(componentsDir, entry.name)).filter(
|
|
1197
|
+
(f) => COMPONENT_EXT.includes(path6.extname(f))
|
|
1140
1198
|
);
|
|
1141
1199
|
if (files.length) return files;
|
|
1142
1200
|
}
|
|
@@ -1144,14 +1202,14 @@ function guessSources(componentsDir, stem) {
|
|
|
1144
1202
|
return [];
|
|
1145
1203
|
}
|
|
1146
1204
|
function loadSystem(paths, urls) {
|
|
1147
|
-
if (!
|
|
1205
|
+
if (!fs6.existsSync(paths.system)) return null;
|
|
1148
1206
|
const issues = [];
|
|
1149
|
-
const configFile =
|
|
1207
|
+
const configFile = path6.join(paths.system, "system.json");
|
|
1150
1208
|
const report = (severity, file, message, at) => issues.push({ severity, file: relToRoot(paths, file), message, at });
|
|
1151
1209
|
let config = null;
|
|
1152
|
-
if (
|
|
1210
|
+
if (fs6.existsSync(configFile)) {
|
|
1153
1211
|
try {
|
|
1154
|
-
const parsed = SystemConfigSchema.safeParse(JSON.parse(
|
|
1212
|
+
const parsed = SystemConfigSchema.safeParse(JSON.parse(fs6.readFileSync(configFile, "utf8")));
|
|
1155
1213
|
if (parsed.success) config = parsed.data;
|
|
1156
1214
|
else for (const issue of describeZodError(parsed.error)) report("error", configFile, issue.message, issue.at);
|
|
1157
1215
|
} catch (error) {
|
|
@@ -1160,36 +1218,36 @@ function loadSystem(paths, urls) {
|
|
|
1160
1218
|
} else {
|
|
1161
1219
|
report("warning", configFile, 'system.json is missing; run `design system init` or add { "name": "\u2026" }');
|
|
1162
1220
|
}
|
|
1163
|
-
const tokensFile =
|
|
1221
|
+
const tokensFile = path6.join(paths.system, "tokens.css");
|
|
1164
1222
|
let stylesheet = null;
|
|
1165
1223
|
if (config?.stylesheet) {
|
|
1166
|
-
stylesheet =
|
|
1167
|
-
if (!
|
|
1224
|
+
stylesheet = path6.resolve(paths.system, config.stylesheet);
|
|
1225
|
+
if (!fs6.existsSync(stylesheet)) {
|
|
1168
1226
|
report("error", configFile, `stylesheet not found: ${config.stylesheet}`, "stylesheet");
|
|
1169
1227
|
stylesheet = null;
|
|
1170
1228
|
}
|
|
1171
1229
|
}
|
|
1172
|
-
const tokenEntry = stylesheet ?? (
|
|
1230
|
+
const tokenEntry = stylesheet ?? (fs6.existsSync(tokensFile) ? tokensFile : null);
|
|
1173
1231
|
const tokens = tokenEntry ? parseTokens(readStylesheets(tokenEntry)) : [];
|
|
1174
1232
|
if (!tokenEntry) report("warning", tokensFile, "no tokens.css: screens get the default Tailwind theme");
|
|
1175
|
-
const specimensDir =
|
|
1176
|
-
const componentsDir =
|
|
1233
|
+
const specimensDir = path6.join(paths.system, "specimens");
|
|
1234
|
+
const componentsDir = path6.join(paths.system, "components");
|
|
1177
1235
|
const components = [];
|
|
1178
1236
|
const specimens = [];
|
|
1179
1237
|
const usedIds = /* @__PURE__ */ new Set();
|
|
1180
1238
|
for (const file of listFiles(specimensDir)) {
|
|
1181
|
-
const ext =
|
|
1239
|
+
const ext = path6.extname(file).toLowerCase();
|
|
1182
1240
|
if (!SPECIMEN_EXT.has(ext)) continue;
|
|
1183
|
-
const stem =
|
|
1241
|
+
const stem = path6.basename(file, ext);
|
|
1184
1242
|
let id2 = slugify(stem);
|
|
1185
1243
|
if (usedIds.has(id2)) id2 = `${id2}-${usedIds.size + 1}`;
|
|
1186
1244
|
usedIds.add(id2);
|
|
1187
|
-
const tags = parseDocTags(
|
|
1188
|
-
const sources = (tags.source ?? []).flatMap((value) => value.split(/[\s,]+/)).filter(Boolean).map((value) =>
|
|
1245
|
+
const tags = parseDocTags(fs6.readFileSync(file, "utf8"));
|
|
1246
|
+
const sources = (tags.source ?? []).flatMap((value) => value.split(/[\s,]+/)).filter(Boolean).map((value) => path6.resolve(path6.dirname(file), value));
|
|
1189
1247
|
for (const source of sources) {
|
|
1190
|
-
if (!
|
|
1248
|
+
if (!fs6.existsSync(source)) report("warning", file, `@source not found: ${path6.relative(paths.system, source)}`);
|
|
1191
1249
|
}
|
|
1192
|
-
const found = sources.filter((source) =>
|
|
1250
|
+
const found = sources.filter((source) => fs6.existsSync(source));
|
|
1193
1251
|
const title = tags.title?.[0] || titleize(stem);
|
|
1194
1252
|
const format = ext === ".html" ? "html" : "module";
|
|
1195
1253
|
components.push({
|
|
@@ -1215,10 +1273,10 @@ function loadSystem(paths, urls) {
|
|
|
1215
1273
|
});
|
|
1216
1274
|
}
|
|
1217
1275
|
const guidelines = [];
|
|
1218
|
-
for (const file of listFiles(
|
|
1219
|
-
if (
|
|
1220
|
-
const stem =
|
|
1221
|
-
const { data, body } = parseFrontmatter(
|
|
1276
|
+
for (const file of listFiles(path6.join(paths.system, "guidelines"))) {
|
|
1277
|
+
if (path6.extname(file).toLowerCase() !== ".md") continue;
|
|
1278
|
+
const stem = path6.basename(file, ".md");
|
|
1279
|
+
const { data, body } = parseFrontmatter(fs6.readFileSync(file, "utf8"));
|
|
1222
1280
|
const prefix = /^(\d+)[-_. ]+(.*)$/.exec(stem);
|
|
1223
1281
|
const order = data.order !== void 0 ? Number(data.order) : prefix ? Number(prefix[1]) : 1e3;
|
|
1224
1282
|
guidelines.push({
|
|
@@ -1230,14 +1288,14 @@ function loadSystem(paths, urls) {
|
|
|
1230
1288
|
}
|
|
1231
1289
|
guidelines.sort((a, b) => a.order - b.order || a.title.localeCompare(b.title));
|
|
1232
1290
|
const assets = [];
|
|
1233
|
-
const assetsDir =
|
|
1291
|
+
const assetsDir = path6.join(paths.system, "assets");
|
|
1234
1292
|
for (const file of listFiles(assetsDir, true)) {
|
|
1235
|
-
const ext =
|
|
1293
|
+
const ext = path6.extname(file).toLowerCase();
|
|
1236
1294
|
assets.push({
|
|
1237
|
-
name:
|
|
1238
|
-
path:
|
|
1295
|
+
name: path6.basename(file),
|
|
1296
|
+
path: path6.relative(paths.system, file).split(path6.sep).join("/"),
|
|
1239
1297
|
url: urls.file(relToDesign(paths, file)),
|
|
1240
|
-
size:
|
|
1298
|
+
size: fs6.statSync(file).size,
|
|
1241
1299
|
kind: IMAGE_EXT.has(ext) ? "image" : FONT_EXT.has(ext) ? "font" : "other"
|
|
1242
1300
|
});
|
|
1243
1301
|
}
|
|
@@ -1273,7 +1331,7 @@ function loadSystem(paths, urls) {
|
|
|
1273
1331
|
},
|
|
1274
1332
|
config,
|
|
1275
1333
|
stylesheet,
|
|
1276
|
-
tokensFile:
|
|
1334
|
+
tokensFile: fs6.existsSync(tokensFile) ? tokensFile : null,
|
|
1277
1335
|
specimens
|
|
1278
1336
|
};
|
|
1279
1337
|
}
|
|
@@ -1289,6 +1347,14 @@ function summarizeSystem(system) {
|
|
|
1289
1347
|
}
|
|
1290
1348
|
|
|
1291
1349
|
// src/core/project.ts
|
|
1350
|
+
function publicProblem(root, value) {
|
|
1351
|
+
const dir = path7.resolve(root, value);
|
|
1352
|
+
if (!fs7.existsSync(dir) || !fs7.statSync(dir).isDirectory()) return `no folder "${value}"`;
|
|
1353
|
+
const real = fs7.realpathSync(dir);
|
|
1354
|
+
const realRoot = fs7.realpathSync(root);
|
|
1355
|
+
if (real === realRoot || !isInside(realRoot, real)) return `"${value}" is not a folder inside the project`;
|
|
1356
|
+
return null;
|
|
1357
|
+
}
|
|
1292
1358
|
var DesignProject = class {
|
|
1293
1359
|
constructor(paths, urls, snapshots = null) {
|
|
1294
1360
|
this.paths = paths;
|
|
@@ -1301,14 +1367,15 @@ var DesignProject = class {
|
|
|
1301
1367
|
configCache = null;
|
|
1302
1368
|
systemCache;
|
|
1303
1369
|
canvasCache = /* @__PURE__ */ new Map();
|
|
1370
|
+
publicCache = null;
|
|
1304
1371
|
config() {
|
|
1305
1372
|
if (this.configCache) return this.configCache;
|
|
1306
1373
|
const issues = [];
|
|
1307
1374
|
let config = {};
|
|
1308
1375
|
const file = this.paths.config;
|
|
1309
|
-
if (
|
|
1376
|
+
if (fs7.existsSync(file)) {
|
|
1310
1377
|
try {
|
|
1311
|
-
const parsed = DesignConfigSchema.safeParse(JSON.parse(
|
|
1378
|
+
const parsed = DesignConfigSchema.safeParse(JSON.parse(fs7.readFileSync(file, "utf8")));
|
|
1312
1379
|
if (parsed.success) config = parsed.data;
|
|
1313
1380
|
else
|
|
1314
1381
|
for (const issue of describeZodError(parsed.error))
|
|
@@ -1321,18 +1388,44 @@ var DesignProject = class {
|
|
|
1321
1388
|
});
|
|
1322
1389
|
}
|
|
1323
1390
|
}
|
|
1391
|
+
if (config.public !== void 0) {
|
|
1392
|
+
const problem = publicProblem(this.paths.root, config.public);
|
|
1393
|
+
if (problem) {
|
|
1394
|
+
issues.push({ severity: "error", file: relToRoot(this.paths, file), at: "public", message: problem });
|
|
1395
|
+
config = { ...config, public: void 0 };
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1324
1398
|
this.configCache = { config, issues };
|
|
1325
1399
|
return this.configCache;
|
|
1326
1400
|
}
|
|
1401
|
+
/** The public folder from design.json, when it is a folder inside the project. */
|
|
1402
|
+
publicDir() {
|
|
1403
|
+
const configured = this.config().config.public;
|
|
1404
|
+
return configured === void 0 ? null : path7.resolve(this.paths.root, configured);
|
|
1405
|
+
}
|
|
1406
|
+
/** The public folder's files, relative to it; `invalidatePublic` when they change. */
|
|
1407
|
+
publicFiles() {
|
|
1408
|
+
if (!this.publicCache) {
|
|
1409
|
+
const dir = this.publicDir();
|
|
1410
|
+
const list = dir ? publicFiles(dir) : [];
|
|
1411
|
+
this.publicCache = { list, set: new Set(list) };
|
|
1412
|
+
}
|
|
1413
|
+
return this.publicCache.list;
|
|
1414
|
+
}
|
|
1415
|
+
/** Whether `rel` is a file the public folder serves (dot-files and links out of it are not). */
|
|
1416
|
+
isPublicFile(rel) {
|
|
1417
|
+
this.publicFiles();
|
|
1418
|
+
return this.publicCache?.set.has(rel) ?? false;
|
|
1419
|
+
}
|
|
1327
1420
|
name() {
|
|
1328
1421
|
const configured = this.config().config.name;
|
|
1329
1422
|
if (configured) return configured;
|
|
1330
1423
|
try {
|
|
1331
|
-
const manifest = JSON.parse(
|
|
1424
|
+
const manifest = JSON.parse(fs7.readFileSync(path7.join(this.paths.root, "package.json"), "utf8"));
|
|
1332
1425
|
if (typeof manifest.name === "string" && manifest.name) return manifest.name;
|
|
1333
1426
|
} catch {
|
|
1334
1427
|
}
|
|
1335
|
-
return
|
|
1428
|
+
return path7.basename(this.paths.root);
|
|
1336
1429
|
}
|
|
1337
1430
|
system() {
|
|
1338
1431
|
if (this.systemCache === void 0) this.systemCache = loadSystem(this.paths, this.urls);
|
|
@@ -1344,7 +1437,7 @@ var DesignProject = class {
|
|
|
1344
1437
|
canvas(id2) {
|
|
1345
1438
|
const cached = this.canvasCache.get(id2);
|
|
1346
1439
|
if (cached) return cached;
|
|
1347
|
-
if (!
|
|
1440
|
+
if (!fs7.existsSync(path7.join(this.paths.canvases, id2))) return null;
|
|
1348
1441
|
const canvas = loadCanvas(this.paths, id2, this.urls, this.snapshots);
|
|
1349
1442
|
this.canvasCache.set(id2, canvas);
|
|
1350
1443
|
return canvas;
|
|
@@ -1382,6 +1475,10 @@ var DesignProject = class {
|
|
|
1382
1475
|
}
|
|
1383
1476
|
invalidateConfig() {
|
|
1384
1477
|
this.configCache = null;
|
|
1478
|
+
this.publicCache = null;
|
|
1479
|
+
}
|
|
1480
|
+
invalidatePublic() {
|
|
1481
|
+
this.publicCache = null;
|
|
1385
1482
|
}
|
|
1386
1483
|
invalidateSystem() {
|
|
1387
1484
|
this.systemCache = void 0;
|
|
@@ -1411,21 +1508,21 @@ var STATIC_URLS = {
|
|
|
1411
1508
|
};
|
|
1412
1509
|
|
|
1413
1510
|
// src/server/entries.ts
|
|
1414
|
-
import
|
|
1415
|
-
import
|
|
1511
|
+
import fs8 from "fs";
|
|
1512
|
+
import path8 from "path";
|
|
1416
1513
|
var SYSTEM_CSS = "system.css";
|
|
1417
1514
|
var HTML_BOOT = "html-boot.js";
|
|
1418
1515
|
function writeIfChanged(file, content) {
|
|
1419
1516
|
try {
|
|
1420
|
-
if (
|
|
1517
|
+
if (fs8.readFileSync(file, "utf8") === content) return false;
|
|
1421
1518
|
} catch {
|
|
1422
1519
|
}
|
|
1423
|
-
|
|
1424
|
-
|
|
1520
|
+
fs8.mkdirSync(path8.dirname(file), { recursive: true });
|
|
1521
|
+
fs8.writeFileSync(file, content);
|
|
1425
1522
|
return true;
|
|
1426
1523
|
}
|
|
1427
1524
|
function importPath(fromDir, file) {
|
|
1428
|
-
const rel = toPosix(
|
|
1525
|
+
const rel = toPosix(path8.relative(fromDir, file));
|
|
1429
1526
|
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
1430
1527
|
}
|
|
1431
1528
|
var css = (file) => JSON.stringify(toPosix(file));
|
|
@@ -1438,10 +1535,10 @@ var Entries = class {
|
|
|
1438
1535
|
return this.project.paths.entries;
|
|
1439
1536
|
}
|
|
1440
1537
|
systemCssFile() {
|
|
1441
|
-
return
|
|
1538
|
+
return path8.join(this.dir, SYSTEM_CSS);
|
|
1442
1539
|
}
|
|
1443
1540
|
entryFile(source) {
|
|
1444
|
-
return
|
|
1541
|
+
return path8.join(this.dir, source.canvas, `${source.id}.js`);
|
|
1445
1542
|
}
|
|
1446
1543
|
/** Tailwind with the design tokens, or the stylesheet system.json names; scans every file in `.design`. */
|
|
1447
1544
|
systemCss() {
|
|
@@ -1458,14 +1555,14 @@ var Entries = class {
|
|
|
1458
1555
|
if (system?.tokensFile) lines.push(`@import ${css(system.tokensFile)};`);
|
|
1459
1556
|
}
|
|
1460
1557
|
lines.push(`@source ${css(paths.design)};`, `@source not ${css(paths.cache)};`);
|
|
1461
|
-
lines.push(`@source not ${css(
|
|
1462
|
-
for (const extra of config.sources ?? []) lines.push(`@source ${css(
|
|
1558
|
+
lines.push(`@source not ${css(path8.join(paths.design, "node_modules"))};`);
|
|
1559
|
+
for (const extra of config.sources ?? []) lines.push(`@source ${css(path8.resolve(paths.root, extra))};`);
|
|
1463
1560
|
return `${lines.join("\n")}
|
|
1464
1561
|
`;
|
|
1465
1562
|
}
|
|
1466
1563
|
entrySource(source) {
|
|
1467
1564
|
const file = this.entryFile(source);
|
|
1468
|
-
const dir =
|
|
1565
|
+
const dir = path8.dirname(file);
|
|
1469
1566
|
const lines = [];
|
|
1470
1567
|
if (source.system) lines.push(`import ${JSON.stringify(importPath(dir, this.systemCssFile()))}`);
|
|
1471
1568
|
if (source.builtin === "typography") {
|
|
@@ -1480,7 +1577,7 @@ var Entries = class {
|
|
|
1480
1577
|
`import * as screen from ${JSON.stringify(importPath(dir, source.file))}`,
|
|
1481
1578
|
"",
|
|
1482
1579
|
`const Screen = screen.default ?? Object.values(screen).find((value) => typeof value === 'function')`,
|
|
1483
|
-
"if (!Screen) throw new Error(" + JSON.stringify(`${
|
|
1580
|
+
"if (!Screen) throw new Error(" + JSON.stringify(`${path8.basename(source.file)} has no component to render: export one as default`) + ")",
|
|
1484
1581
|
"hmr(import.meta.hot)",
|
|
1485
1582
|
"mount(Screen)"
|
|
1486
1583
|
);
|
|
@@ -1493,14 +1590,14 @@ ${lines.join("\n")}
|
|
|
1493
1590
|
sync() {
|
|
1494
1591
|
writeIfChanged(this.systemCssFile(), this.systemCss());
|
|
1495
1592
|
writeIfChanged(
|
|
1496
|
-
|
|
1593
|
+
path8.join(this.dir, HTML_BOOT),
|
|
1497
1594
|
`// Generated by foss-design.
|
|
1498
1595
|
import { boot, hmr } from '@design/runtime'
|
|
1499
1596
|
boot()
|
|
1500
1597
|
hmr(import.meta.hot)
|
|
1501
1598
|
`
|
|
1502
1599
|
);
|
|
1503
|
-
const wanted = /* @__PURE__ */ new Set([this.systemCssFile(),
|
|
1600
|
+
const wanted = /* @__PURE__ */ new Set([this.systemCssFile(), path8.join(this.dir, HTML_BOOT)]);
|
|
1504
1601
|
for (const source of this.project.sources()) {
|
|
1505
1602
|
if (source.format !== "module") continue;
|
|
1506
1603
|
const file = this.entryFile(source);
|
|
@@ -1509,18 +1606,27 @@ hmr(import.meta.hot)
|
|
|
1509
1606
|
}
|
|
1510
1607
|
this.prune(this.dir, wanted);
|
|
1511
1608
|
}
|
|
1512
|
-
/**
|
|
1609
|
+
/**
|
|
1610
|
+
* What the dev server scans for dependencies before any frame loads: the generated entries,
|
|
1611
|
+
* the HTML screens and the boot they load. A dependency found only once frames load makes
|
|
1612
|
+
* Vite bundle again and fail the requests in flight (504 Outdated Optimize Dep).
|
|
1613
|
+
*/
|
|
1513
1614
|
files() {
|
|
1514
|
-
|
|
1615
|
+
const files = [path8.join(this.dir, HTML_BOOT)];
|
|
1616
|
+
for (const source of this.project.sources()) {
|
|
1617
|
+
if (source.format === "module") files.push(this.entryFile(source));
|
|
1618
|
+
else if (!source.builtin && fs8.existsSync(source.file)) files.push(source.file);
|
|
1619
|
+
}
|
|
1620
|
+
return files;
|
|
1515
1621
|
}
|
|
1516
1622
|
prune(dir, wanted) {
|
|
1517
|
-
if (!
|
|
1518
|
-
for (const entry of
|
|
1519
|
-
const full =
|
|
1623
|
+
if (!fs8.existsSync(dir)) return;
|
|
1624
|
+
for (const entry of fs8.readdirSync(dir, { withFileTypes: true })) {
|
|
1625
|
+
const full = path8.join(dir, entry.name);
|
|
1520
1626
|
if (entry.isDirectory()) {
|
|
1521
1627
|
this.prune(full, wanted);
|
|
1522
|
-
if (
|
|
1523
|
-
} else if (!wanted.has(full))
|
|
1628
|
+
if (fs8.readdirSync(full).length === 0) fs8.rmdirSync(full);
|
|
1629
|
+
} else if (!wanted.has(full)) fs8.rmSync(full);
|
|
1524
1630
|
}
|
|
1525
1631
|
}
|
|
1526
1632
|
};
|
|
@@ -1536,7 +1642,8 @@ function headTags(head) {
|
|
|
1536
1642
|
props: source.props,
|
|
1537
1643
|
theme: source.theme,
|
|
1538
1644
|
snapshots: head.snapshots,
|
|
1539
|
-
autoHeight: head.autoHeight
|
|
1645
|
+
autoHeight: head.autoHeight,
|
|
1646
|
+
public: head.public
|
|
1540
1647
|
};
|
|
1541
1648
|
const fallback = source.theme ?? head.theme;
|
|
1542
1649
|
const theme2 = `(function(){var q=new URLSearchParams(location.search).get('theme');var t=${JSON.stringify(source.theme ?? null)}||(q==='dark'||q==='light'?q:${JSON.stringify(fallback)});var r=document.documentElement;r.dataset.theme=t;r.classList.toggle('dark',t==='dark');r.style.colorScheme=t})()`;
|
|
@@ -1587,68 +1694,68 @@ ${html}
|
|
|
1587
1694
|
}
|
|
1588
1695
|
|
|
1589
1696
|
// src/server/links.ts
|
|
1590
|
-
import
|
|
1591
|
-
import
|
|
1697
|
+
import fs9 from "fs";
|
|
1698
|
+
import path9 from "path";
|
|
1592
1699
|
var SHIPPED_PACKAGES = ["react", "react-dom", "motion", "lucide-react", "clsx", "tailwind-merge"];
|
|
1593
1700
|
function findPackageDir(fromDir, name, skip) {
|
|
1594
|
-
let dir =
|
|
1701
|
+
let dir = path9.resolve(fromDir);
|
|
1595
1702
|
for (; ; ) {
|
|
1596
|
-
const modules =
|
|
1597
|
-
const candidate =
|
|
1598
|
-
if (modules !== skip &&
|
|
1599
|
-
const parent =
|
|
1703
|
+
const modules = path9.join(dir, "node_modules");
|
|
1704
|
+
const candidate = path9.join(modules, name);
|
|
1705
|
+
if (modules !== skip && fs9.existsSync(path9.join(candidate, "package.json"))) return candidate;
|
|
1706
|
+
const parent = path9.dirname(dir);
|
|
1600
1707
|
if (parent === dir) return null;
|
|
1601
1708
|
dir = parent;
|
|
1602
1709
|
}
|
|
1603
1710
|
}
|
|
1604
1711
|
function isLink(file) {
|
|
1605
1712
|
try {
|
|
1606
|
-
return
|
|
1713
|
+
return fs9.lstatSync(file).isSymbolicLink();
|
|
1607
1714
|
} catch {
|
|
1608
1715
|
return false;
|
|
1609
1716
|
}
|
|
1610
1717
|
}
|
|
1611
1718
|
function linkShippedPackages(paths) {
|
|
1612
|
-
const modules =
|
|
1719
|
+
const modules = path9.join(paths.design, "node_modules");
|
|
1613
1720
|
const linked = [];
|
|
1614
1721
|
const allow = /* @__PURE__ */ new Set();
|
|
1615
1722
|
for (const name of SHIPPED_PACKAGES) {
|
|
1616
|
-
const link =
|
|
1617
|
-
const placedByUser =
|
|
1723
|
+
const link = path9.join(modules, name);
|
|
1724
|
+
const placedByUser = fs9.existsSync(link) && !isLink(link);
|
|
1618
1725
|
const own = placedByUser ? link : findPackageDir(paths.design, name, modules);
|
|
1619
1726
|
if (own) {
|
|
1620
|
-
allow.add(
|
|
1621
|
-
if (isLink(link))
|
|
1727
|
+
allow.add(path9.dirname(fs9.realpathSync(own)));
|
|
1728
|
+
if (isLink(link)) fs9.unlinkSync(link);
|
|
1622
1729
|
continue;
|
|
1623
1730
|
}
|
|
1624
1731
|
const shipped = findPackageDir(PKG_ROOT, name);
|
|
1625
1732
|
if (!shipped) continue;
|
|
1626
|
-
const target2 =
|
|
1627
|
-
allow.add(
|
|
1733
|
+
const target2 = fs9.realpathSync(shipped);
|
|
1734
|
+
allow.add(path9.dirname(target2));
|
|
1628
1735
|
let current2 = null;
|
|
1629
1736
|
try {
|
|
1630
|
-
current2 =
|
|
1737
|
+
current2 = fs9.realpathSync(link);
|
|
1631
1738
|
} catch {
|
|
1632
1739
|
}
|
|
1633
1740
|
if (current2 !== target2) {
|
|
1634
|
-
if (isLink(link))
|
|
1635
|
-
|
|
1636
|
-
|
|
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");
|
|
1637
1744
|
}
|
|
1638
1745
|
linked.push(name);
|
|
1639
1746
|
}
|
|
1640
|
-
const self =
|
|
1641
|
-
const target =
|
|
1747
|
+
const self = path9.join(modules, "foss-design");
|
|
1748
|
+
const target = fs9.realpathSync(PKG_ROOT);
|
|
1642
1749
|
let current = null;
|
|
1643
1750
|
try {
|
|
1644
|
-
current =
|
|
1751
|
+
current = fs9.realpathSync(self);
|
|
1645
1752
|
} catch {
|
|
1646
1753
|
}
|
|
1647
1754
|
if (current !== target) {
|
|
1648
|
-
if (isLink(self))
|
|
1649
|
-
if (!
|
|
1650
|
-
|
|
1651
|
-
|
|
1755
|
+
if (isLink(self)) fs9.unlinkSync(self);
|
|
1756
|
+
if (!fs9.existsSync(self)) {
|
|
1757
|
+
fs9.mkdirSync(modules, { recursive: true });
|
|
1758
|
+
fs9.symlinkSync(target, self, process.platform === "win32" ? "junction" : "dir");
|
|
1652
1759
|
}
|
|
1653
1760
|
}
|
|
1654
1761
|
allow.add(target);
|
|
@@ -1656,7 +1763,9 @@ function linkShippedPackages(paths) {
|
|
|
1656
1763
|
}
|
|
1657
1764
|
|
|
1658
1765
|
// src/server/vite.ts
|
|
1659
|
-
import
|
|
1766
|
+
import { createHash as createHash2 } from "crypto";
|
|
1767
|
+
import fs10 from "fs";
|
|
1768
|
+
import path10 from "path";
|
|
1660
1769
|
import tailwindcss from "@tailwindcss/vite";
|
|
1661
1770
|
import react from "@vitejs/plugin-react";
|
|
1662
1771
|
var escapeRegExp = (text2) => text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -1668,21 +1777,40 @@ function aliases(project) {
|
|
|
1668
1777
|
{ find: /^@system\//, replacement: `${toPosix(paths.system)}/` }
|
|
1669
1778
|
];
|
|
1670
1779
|
for (const [key, target] of Object.entries(project.config().config.alias ?? {})) {
|
|
1671
|
-
const replacement = toPosix(
|
|
1780
|
+
const replacement = toPosix(path10.resolve(paths.root, target));
|
|
1672
1781
|
list.push(
|
|
1673
1782
|
key.endsWith("/") ? { find: new RegExp(`^${escapeRegExp(key)}`), replacement: `${replacement.replace(/\/$/, "")}/` } : { find: new RegExp(`^${escapeRegExp(key)}(?=/|$)`), replacement }
|
|
1674
1783
|
);
|
|
1675
1784
|
}
|
|
1676
1785
|
return list;
|
|
1677
1786
|
}
|
|
1787
|
+
function dropStaleDepCache(paths) {
|
|
1788
|
+
const dir = path10.join(paths.cache, "vite");
|
|
1789
|
+
const stamp = path10.join(paths.cache, "vite-runtime");
|
|
1790
|
+
let runtime = "";
|
|
1791
|
+
try {
|
|
1792
|
+
runtime = createHash2("sha256").update(fs10.readFileSync(PKG.runtime)).digest("hex");
|
|
1793
|
+
} catch {
|
|
1794
|
+
}
|
|
1795
|
+
let seen = "";
|
|
1796
|
+
try {
|
|
1797
|
+
seen = fs10.readFileSync(stamp, "utf8");
|
|
1798
|
+
} catch {
|
|
1799
|
+
}
|
|
1800
|
+
if (seen === runtime) return;
|
|
1801
|
+
fs10.rmSync(dir, { recursive: true, force: true });
|
|
1802
|
+
fs10.mkdirSync(paths.cache, { recursive: true });
|
|
1803
|
+
fs10.writeFileSync(stamp, runtime);
|
|
1804
|
+
}
|
|
1678
1805
|
function baseConfig(project) {
|
|
1679
1806
|
const { paths } = project;
|
|
1680
1807
|
return {
|
|
1681
1808
|
configFile: false,
|
|
1682
1809
|
envDir: false,
|
|
1683
1810
|
root: paths.design,
|
|
1684
|
-
cacheDir:
|
|
1685
|
-
|
|
1811
|
+
cacheDir: path10.join(paths.cache, "vite"),
|
|
1812
|
+
// design.json `public`: served under the frames' base, so HTML and CSS paths such as `/logo.png` resolve.
|
|
1813
|
+
publicDir: project.publicDir() ?? false,
|
|
1686
1814
|
logLevel: "warn",
|
|
1687
1815
|
clearScreen: false,
|
|
1688
1816
|
resolve: {
|
|
@@ -1700,7 +1828,7 @@ var SOURCE_EXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".jsx", ".mts", ".cts",
|
|
|
1700
1828
|
var BUNDLED_REF = /<(script|link|img|source|video|audio|image|use|input)\b[^>]*>/gi;
|
|
1701
1829
|
var encodePath2 = (rel) => rel.split("/").map(encodeURIComponent).join("/");
|
|
1702
1830
|
function relUrl(fromDir, file) {
|
|
1703
|
-
const rel = toPosix(
|
|
1831
|
+
const rel = toPosix(path11.relative(fromDir, file));
|
|
1704
1832
|
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
1705
1833
|
}
|
|
1706
1834
|
function staticSnapshots(paths) {
|
|
@@ -1710,7 +1838,7 @@ function staticSnapshots(paths) {
|
|
|
1710
1838
|
let data = metaCache.get(canvas);
|
|
1711
1839
|
if (!data) {
|
|
1712
1840
|
try {
|
|
1713
|
-
data = JSON.parse(
|
|
1841
|
+
data = JSON.parse(fs11.readFileSync(path11.join(paths.snapshots, canvas, "meta.json"), "utf8"));
|
|
1714
1842
|
} catch {
|
|
1715
1843
|
}
|
|
1716
1844
|
data ??= {};
|
|
@@ -1724,8 +1852,8 @@ function staticSnapshots(paths) {
|
|
|
1724
1852
|
const urls = {};
|
|
1725
1853
|
const thumbs = {};
|
|
1726
1854
|
for (const theme2 of THEMES) {
|
|
1727
|
-
const file =
|
|
1728
|
-
if (!entry[theme2] || !
|
|
1855
|
+
const file = path11.join(paths.snapshots, canvas, `${id2}.${theme2}.png`);
|
|
1856
|
+
if (!entry[theme2] || !fs11.existsSync(file)) continue;
|
|
1729
1857
|
const rel = `_snap/${canvas}/${id2}.${theme2}.png`;
|
|
1730
1858
|
copies.set(file, rel);
|
|
1731
1859
|
urls[theme2] = `${encodePath2(rel)}?v=${entry[theme2]}`;
|
|
@@ -1749,29 +1877,29 @@ function rewriteRefs(html, originalDir, stagedDir) {
|
|
|
1749
1877
|
(whole, attr, quote, value) => {
|
|
1750
1878
|
if (!value || /^(?:[a-z][a-z0-9+.-]*:|\/|#|\{)/i.test(value)) return whole;
|
|
1751
1879
|
const [pathname = "", suffix = ""] = /^([^?#]*)(.*)$/.exec(value).slice(1);
|
|
1752
|
-
const target =
|
|
1753
|
-
if (!
|
|
1880
|
+
const target = path11.resolve(originalDir, decodeURIComponent(pathname));
|
|
1881
|
+
if (!fs11.existsSync(target)) return whole;
|
|
1754
1882
|
return `${attr}${quote}${relUrl(stagedDir, target)}${suffix}${quote}`;
|
|
1755
1883
|
}
|
|
1756
1884
|
);
|
|
1757
1885
|
});
|
|
1758
1886
|
}
|
|
1759
1887
|
function copyTree(from, to, filter) {
|
|
1760
|
-
if (!
|
|
1761
|
-
for (const entry of
|
|
1888
|
+
if (!fs11.existsSync(from)) return;
|
|
1889
|
+
for (const entry of fs11.readdirSync(from, { withFileTypes: true })) {
|
|
1762
1890
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
1763
|
-
const source =
|
|
1764
|
-
const target =
|
|
1891
|
+
const source = path11.join(from, entry.name);
|
|
1892
|
+
const target = path11.join(to, entry.name);
|
|
1765
1893
|
if (entry.isDirectory()) copyTree(source, target, filter);
|
|
1766
|
-
else if (filter(source) && !
|
|
1767
|
-
|
|
1768
|
-
|
|
1894
|
+
else if (filter(source) && !fs11.existsSync(target)) {
|
|
1895
|
+
fs11.mkdirSync(path11.dirname(target), { recursive: true });
|
|
1896
|
+
fs11.copyFileSync(source, target);
|
|
1769
1897
|
}
|
|
1770
1898
|
}
|
|
1771
1899
|
}
|
|
1772
1900
|
function writeJson(file, value) {
|
|
1773
|
-
|
|
1774
|
-
|
|
1901
|
+
fs11.mkdirSync(path11.dirname(file), { recursive: true });
|
|
1902
|
+
fs11.writeFileSync(file, JSON.stringify(value));
|
|
1775
1903
|
}
|
|
1776
1904
|
async function buildSite(paths, out, options = {}) {
|
|
1777
1905
|
const snapshots = staticSnapshots(paths);
|
|
@@ -1787,42 +1915,46 @@ async function buildSite(paths, out, options = {}) {
|
|
|
1787
1915
|
const entries = new Entries(project);
|
|
1788
1916
|
entries.sync();
|
|
1789
1917
|
linkShippedPackages(paths);
|
|
1790
|
-
const
|
|
1791
|
-
|
|
1918
|
+
const publicDir = project.publicDir();
|
|
1919
|
+
const publicFiles2 = project.publicFiles();
|
|
1920
|
+
const staging = path11.join(paths.cache, "build", "src");
|
|
1921
|
+
fs11.rmSync(staging, { recursive: true, force: true });
|
|
1792
1922
|
const fonts = loadedSystem?.doc.fonts ?? [];
|
|
1793
1923
|
const sources = [...system?.specimens ?? [], ...canvases.flatMap((canvas) => canvas.screens)];
|
|
1794
1924
|
const inputs = [];
|
|
1795
1925
|
const htmlScreens = [];
|
|
1796
1926
|
for (const source of sources) {
|
|
1797
|
-
if (!source.builtin && !
|
|
1798
|
-
const siteDir =
|
|
1799
|
-
const dir =
|
|
1800
|
-
const file =
|
|
1801
|
-
|
|
1927
|
+
if (!source.builtin && !fs11.existsSync(source.file)) continue;
|
|
1928
|
+
const siteDir = path11.join("_s", source.canvas, source.id);
|
|
1929
|
+
const dir = path11.join(staging, siteDir);
|
|
1930
|
+
const file = path11.join(dir, "index.html");
|
|
1931
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
1802
1932
|
const canvasTheme = source.canvas === SYSTEM_CANVAS ? void 0 : project.canvas(source.canvas)?.doc.theme;
|
|
1803
1933
|
const head = {
|
|
1804
1934
|
source,
|
|
1805
1935
|
theme: canvasTheme ?? "light",
|
|
1806
1936
|
fonts,
|
|
1807
1937
|
snapshots: false,
|
|
1808
|
-
autoHeight: source.autoHeight ?? false
|
|
1938
|
+
autoHeight: source.autoHeight ?? false,
|
|
1939
|
+
// The public folder lands at the site root, which frames reach through `../`.
|
|
1940
|
+
public: publicFiles2.length ? { base: `${toPosix(path11.relative(dir, staging))}/`, files: publicFiles2 } : void 0
|
|
1809
1941
|
};
|
|
1810
1942
|
if (source.format === "module") {
|
|
1811
|
-
|
|
1943
|
+
fs11.writeFileSync(file, moduleShell(head, relUrl(dir, entries.entryFile(source))));
|
|
1812
1944
|
} else {
|
|
1813
|
-
const raw = rewriteRefs(
|
|
1945
|
+
const raw = rewriteRefs(fs11.readFileSync(source.file, "utf8"), path11.dirname(source.file), dir);
|
|
1814
1946
|
const withSystem = source.system && !optsOutOfSystem(raw);
|
|
1815
1947
|
const html = injectIntoHtml(raw, {
|
|
1816
1948
|
...head,
|
|
1817
1949
|
systemCss: withSystem ? relUrl(dir, entries.systemCssFile()) : void 0,
|
|
1818
|
-
boot: relUrl(dir,
|
|
1950
|
+
boot: relUrl(dir, path11.join(entries.dir, HTML_BOOT))
|
|
1819
1951
|
});
|
|
1820
|
-
|
|
1952
|
+
fs11.writeFileSync(file, html);
|
|
1821
1953
|
htmlScreens.push({ source, dir: siteDir });
|
|
1822
1954
|
}
|
|
1823
1955
|
inputs.push(file);
|
|
1824
1956
|
}
|
|
1825
|
-
|
|
1957
|
+
fs11.mkdirSync(out, { recursive: true });
|
|
1826
1958
|
if (inputs.length) {
|
|
1827
1959
|
const base = baseConfig(project);
|
|
1828
1960
|
await build({
|
|
@@ -1837,18 +1969,20 @@ async function buildSite(paths, out, options = {}) {
|
|
|
1837
1969
|
target: "es2022",
|
|
1838
1970
|
reportCompressedSize: false,
|
|
1839
1971
|
chunkSizeWarningLimit: 8e3,
|
|
1972
|
+
// Copied below, without dot-files and links out of the folder, and after the site's own files.
|
|
1973
|
+
copyPublicDir: false,
|
|
1840
1974
|
rolldownOptions: { input: inputs }
|
|
1841
1975
|
}
|
|
1842
1976
|
});
|
|
1843
1977
|
} else {
|
|
1844
|
-
|
|
1845
|
-
|
|
1978
|
+
fs11.rmSync(out, { recursive: true, force: true });
|
|
1979
|
+
fs11.mkdirSync(out, { recursive: true });
|
|
1846
1980
|
}
|
|
1847
1981
|
for (const { source, dir } of htmlScreens) {
|
|
1848
1982
|
copyTree(
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
(file) => !SOURCE_EXT.has(
|
|
1983
|
+
path11.dirname(source.file),
|
|
1984
|
+
path11.join(out, dir),
|
|
1985
|
+
(file) => !SOURCE_EXT.has(path11.extname(file).toLowerCase())
|
|
1852
1986
|
);
|
|
1853
1987
|
}
|
|
1854
1988
|
const files = /* @__PURE__ */ new Set();
|
|
@@ -1860,51 +1994,59 @@ async function buildSite(paths, out, options = {}) {
|
|
|
1860
1994
|
for (const asset of system?.doc.assets ?? []) files.add(asset.url);
|
|
1861
1995
|
for (const url of files) {
|
|
1862
1996
|
const rel = url.slice("_f/".length).split("/").map(decodeURIComponent).join("/");
|
|
1863
|
-
const from =
|
|
1864
|
-
if (!
|
|
1865
|
-
const to =
|
|
1866
|
-
|
|
1867
|
-
|
|
1997
|
+
const from = path11.join(paths.design, rel);
|
|
1998
|
+
if (!fs11.existsSync(from)) continue;
|
|
1999
|
+
const to = path11.join(out, "_f", rel);
|
|
2000
|
+
fs11.mkdirSync(path11.dirname(to), { recursive: true });
|
|
2001
|
+
fs11.copyFileSync(from, to);
|
|
1868
2002
|
}
|
|
1869
2003
|
const info = project.info(true);
|
|
1870
2004
|
const snapshotDirs = canvasIds.map((id2) => `_snap/${id2}/`);
|
|
1871
2005
|
for (const [from, rel] of snapshots.copies) {
|
|
1872
2006
|
if (!snapshotDirs.some((dir) => rel.startsWith(dir))) continue;
|
|
1873
|
-
const to =
|
|
1874
|
-
|
|
1875
|
-
|
|
2007
|
+
const to = path11.join(out, rel);
|
|
2008
|
+
fs11.mkdirSync(path11.dirname(to), { recursive: true });
|
|
2009
|
+
fs11.copyFileSync(from, to);
|
|
1876
2010
|
}
|
|
1877
2011
|
if (!system) info.system = null;
|
|
1878
2012
|
info.canvases = info.canvases.filter((canvas) => canvasIds.includes(canvas.id));
|
|
1879
|
-
writeJson(
|
|
1880
|
-
for (const canvas of canvases) writeJson(
|
|
2013
|
+
writeJson(path11.join(out, "api", "project.json"), info);
|
|
2014
|
+
for (const canvas of canvases) writeJson(path11.join(out, "api", "canvas", `${canvas.doc.id}.json`), canvas.doc);
|
|
1881
2015
|
if (system) {
|
|
1882
|
-
writeJson(
|
|
2016
|
+
writeJson(path11.join(out, "api", "system.json"), system.doc);
|
|
1883
2017
|
const texts = {};
|
|
1884
2018
|
for (const component of system.doc.components) {
|
|
1885
2019
|
for (const rel of [component.specimen, ...component.sources]) {
|
|
1886
|
-
const file =
|
|
1887
|
-
if (!(rel in texts) &&
|
|
2020
|
+
const file = path11.join(paths.root, rel);
|
|
2021
|
+
if (!(rel in texts) && fs11.existsSync(file)) texts[rel] = fs11.readFileSync(file, "utf8");
|
|
1888
2022
|
}
|
|
1889
2023
|
}
|
|
1890
|
-
writeJson(
|
|
2024
|
+
writeJson(path11.join(out, "api", "sources.json"), texts);
|
|
1891
2025
|
}
|
|
1892
|
-
|
|
2026
|
+
fs11.rmSync(staging, { recursive: true, force: true });
|
|
1893
2027
|
const errors = [
|
|
1894
2028
|
...only ? [] : project.config().issues,
|
|
1895
2029
|
...system?.doc.issues ?? [],
|
|
1896
2030
|
...canvases.flatMap((canvas) => canvas.doc.issues)
|
|
1897
2031
|
].filter((issue) => issue.severity === "error").length;
|
|
1898
2032
|
const { absoluteAssets } = options.includeViewer === false ? { absoluteAssets: false } : copyViewer(PKG.viewer, out);
|
|
2033
|
+
if (publicDir) {
|
|
2034
|
+
for (const rel of publicFiles2) {
|
|
2035
|
+
const to = path11.join(out, rel);
|
|
2036
|
+
if (fs11.existsSync(to)) continue;
|
|
2037
|
+
fs11.mkdirSync(path11.dirname(to), { recursive: true });
|
|
2038
|
+
fs11.copyFileSync(path11.join(publicDir, rel), to);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
1899
2041
|
return { out, canvases: canvasIds, frames: inputs.length, errors, absoluteAssets };
|
|
1900
2042
|
}
|
|
1901
2043
|
function copyViewer(viewerDir, out) {
|
|
1902
2044
|
copyTree(viewerDir, out, () => true);
|
|
1903
|
-
const index =
|
|
1904
|
-
const html =
|
|
2045
|
+
const index = path11.join(out, "index.html");
|
|
2046
|
+
const html = fs11.readFileSync(index, "utf8");
|
|
1905
2047
|
const flag2 = "<script>window.__DESIGN_STATIC__=true</script>";
|
|
1906
2048
|
const head = /<head(\s[^>]*)?>/i.exec(html);
|
|
1907
|
-
|
|
2049
|
+
fs11.writeFileSync(
|
|
1908
2050
|
index,
|
|
1909
2051
|
head ? html.slice(0, head.index + head[0].length) + flag2 + html.slice(head.index + head[0].length) : flag2 + html
|
|
1910
2052
|
);
|
|
@@ -1986,8 +2128,8 @@ function progress() {
|
|
|
1986
2128
|
// src/cli/commands/build.ts
|
|
1987
2129
|
function pack(out) {
|
|
1988
2130
|
const archive = `${out.replace(/[\\/]+$/, "")}.tar.gz`;
|
|
1989
|
-
|
|
1990
|
-
const entries =
|
|
2131
|
+
fs12.rmSync(archive, { force: true });
|
|
2132
|
+
const entries = fs12.readdirSync(out);
|
|
1991
2133
|
const result = spawnSync("tar", ["-czf", archive, "-C", out, ...entries], {
|
|
1992
2134
|
stdio: ["ignore", "ignore", "pipe"],
|
|
1993
2135
|
env: { ...process.env, COPYFILE_DISABLE: "1" }
|
|
@@ -1997,10 +2139,10 @@ function pack(out) {
|
|
|
1997
2139
|
return archive;
|
|
1998
2140
|
}
|
|
1999
2141
|
async function runBuild(paths, canvases, options) {
|
|
2000
|
-
if (!
|
|
2142
|
+
if (!fs12.existsSync(path12.join(PKG.viewer, "index.html"))) {
|
|
2001
2143
|
throw new CliError("The viewer is missing from this foss-design install (dist/viewer). Rebuild or reinstall it.");
|
|
2002
2144
|
}
|
|
2003
|
-
const out =
|
|
2145
|
+
const out = path12.resolve(options.out ?? path12.join(paths.cache, "site"));
|
|
2004
2146
|
if (out === paths.root || out === paths.design) throw new CliError(`Refusing to build into ${out}`);
|
|
2005
2147
|
print(dim(`Building ${canvases.length ? canvases.join(", ") : "every canvas"} and the design system\u2026`));
|
|
2006
2148
|
let result;
|
|
@@ -2024,9 +2166,9 @@ async function runBuild(paths, canvases, options) {
|
|
|
2024
2166
|
import os from "os";
|
|
2025
2167
|
|
|
2026
2168
|
// src/cloud/units.ts
|
|
2027
|
-
import { createHash as
|
|
2028
|
-
import
|
|
2029
|
-
import
|
|
2169
|
+
import { createHash as createHash3 } from "crypto";
|
|
2170
|
+
import fs13 from "fs";
|
|
2171
|
+
import path13 from "path";
|
|
2030
2172
|
var SYSTEM_UNIT = "system";
|
|
2031
2173
|
var CANVAS_PREFIX = "canvas/";
|
|
2032
2174
|
var LOCAL_ONLY = /* @__PURE__ */ new Set(["cloud.json", "node_modules"]);
|
|
@@ -2054,7 +2196,7 @@ function caseClashes(keys) {
|
|
|
2054
2196
|
}
|
|
2055
2197
|
function unitRoot(paths, key) {
|
|
2056
2198
|
const id2 = unitCanvasId(key);
|
|
2057
|
-
return id2 === null ? paths.design :
|
|
2199
|
+
return id2 === null ? paths.design : path13.join(paths.canvases, id2);
|
|
2058
2200
|
}
|
|
2059
2201
|
function unitRelative(key, rel) {
|
|
2060
2202
|
const id2 = unitCanvasId(key);
|
|
@@ -2069,10 +2211,10 @@ function isUnitPath(key, rel) {
|
|
|
2069
2211
|
return id2 !== null && segments[0] === "canvas" && segments[1] === id2 && segments.length > 2;
|
|
2070
2212
|
}
|
|
2071
2213
|
function hashBuffer(data) {
|
|
2072
|
-
return
|
|
2214
|
+
return createHash3("sha256").update(data).digest("hex");
|
|
2073
2215
|
}
|
|
2074
2216
|
function hashFile(file) {
|
|
2075
|
-
const data =
|
|
2217
|
+
const data = fs13.readFileSync(file);
|
|
2076
2218
|
return { hash: hashBuffer(data), size: data.length };
|
|
2077
2219
|
}
|
|
2078
2220
|
var warnedLinks = /* @__PURE__ */ new Set();
|
|
@@ -2085,34 +2227,34 @@ function warnLink(rel) {
|
|
|
2085
2227
|
function walk(dir, base, out) {
|
|
2086
2228
|
let entries;
|
|
2087
2229
|
try {
|
|
2088
|
-
entries =
|
|
2230
|
+
entries = fs13.readdirSync(dir, { withFileTypes: true });
|
|
2089
2231
|
} catch {
|
|
2090
2232
|
return;
|
|
2091
2233
|
}
|
|
2092
2234
|
for (const entry of entries) {
|
|
2093
2235
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
2094
|
-
const full =
|
|
2236
|
+
const full = path13.join(dir, entry.name);
|
|
2095
2237
|
let isFile = entry.isFile();
|
|
2096
2238
|
let isDir = entry.isDirectory();
|
|
2097
2239
|
if (entry.isSymbolicLink()) {
|
|
2098
2240
|
isDir = false;
|
|
2099
2241
|
isFile = false;
|
|
2100
2242
|
try {
|
|
2101
|
-
const target =
|
|
2102
|
-
const root =
|
|
2103
|
-
if (target.startsWith(root +
|
|
2104
|
-
else warnLink(toPosix(
|
|
2243
|
+
const target = fs13.realpathSync(full);
|
|
2244
|
+
const root = fs13.realpathSync(base);
|
|
2245
|
+
if (target.startsWith(root + path13.sep)) isFile = fs13.statSync(target).isFile();
|
|
2246
|
+
else warnLink(toPosix(path13.relative(base, full)));
|
|
2105
2247
|
} catch {
|
|
2106
2248
|
}
|
|
2107
2249
|
}
|
|
2108
2250
|
if (isDir) walk(full, base, out);
|
|
2109
|
-
else if (isFile) out[toPosix(
|
|
2251
|
+
else if (isFile) out[toPosix(path13.relative(base, full))] = hashFile(full);
|
|
2110
2252
|
}
|
|
2111
2253
|
}
|
|
2112
2254
|
function unitManifest(paths, key) {
|
|
2113
2255
|
const manifest = {};
|
|
2114
2256
|
if (key === SYSTEM_UNIT) {
|
|
2115
|
-
if (
|
|
2257
|
+
if (fs13.existsSync(paths.config) && fs13.statSync(paths.config).isFile())
|
|
2116
2258
|
manifest["design.json"] = hashFile(paths.config);
|
|
2117
2259
|
walk(paths.system, paths.design, manifest);
|
|
2118
2260
|
} else walk(unitRoot(paths, key), paths.design, manifest);
|
|
@@ -2121,13 +2263,13 @@ function unitManifest(paths, key) {
|
|
|
2121
2263
|
function buildManifest(dir) {
|
|
2122
2264
|
const manifest = {};
|
|
2123
2265
|
const visit = (current) => {
|
|
2124
|
-
for (const entry of
|
|
2125
|
-
const full =
|
|
2266
|
+
for (const entry of fs13.readdirSync(current, { withFileTypes: true })) {
|
|
2267
|
+
const full = path13.join(current, entry.name);
|
|
2126
2268
|
if (entry.isDirectory()) visit(full);
|
|
2127
|
-
else if (entry.isFile()) manifest[toPosix(
|
|
2269
|
+
else if (entry.isFile()) manifest[toPosix(path13.relative(dir, full))] = hashFile(full);
|
|
2128
2270
|
}
|
|
2129
2271
|
};
|
|
2130
|
-
if (
|
|
2272
|
+
if (fs13.existsSync(dir)) visit(dir);
|
|
2131
2273
|
return sortManifest(manifest);
|
|
2132
2274
|
}
|
|
2133
2275
|
function sortManifest(manifest) {
|
|
@@ -2136,18 +2278,18 @@ function sortManifest(manifest) {
|
|
|
2136
2278
|
function scanLocal(paths) {
|
|
2137
2279
|
const units = /* @__PURE__ */ new Map();
|
|
2138
2280
|
const notSynced = [];
|
|
2139
|
-
if (!
|
|
2281
|
+
if (!fs13.existsSync(paths.design)) return { units, notSynced };
|
|
2140
2282
|
const system = unitManifest(paths, SYSTEM_UNIT);
|
|
2141
2283
|
if (Object.keys(system).length) units.set(SYSTEM_UNIT, { key: SYSTEM_UNIT, kind: "system", manifest: system });
|
|
2142
|
-
for (const entry of
|
|
2284
|
+
for (const entry of fs13.readdirSync(paths.design, { withFileTypes: true })) {
|
|
2143
2285
|
const { name } = entry;
|
|
2144
2286
|
if (name.startsWith(".") || LOCAL_ONLY.has(name)) continue;
|
|
2145
2287
|
if (name === "design.json" && entry.isFile()) continue;
|
|
2146
2288
|
if ((name === "system" || name === "canvas") && entry.isDirectory()) continue;
|
|
2147
2289
|
notSynced.push(entry.isDirectory() ? `${name}/` : name);
|
|
2148
2290
|
}
|
|
2149
|
-
if (
|
|
2150
|
-
for (const entry of
|
|
2291
|
+
if (fs13.existsSync(paths.canvases) && fs13.statSync(paths.canvases).isDirectory()) {
|
|
2292
|
+
for (const entry of fs13.readdirSync(paths.canvases, { withFileTypes: true })) {
|
|
2151
2293
|
if (entry.name.startsWith(".")) continue;
|
|
2152
2294
|
if (!entry.isDirectory() || !ID_PATTERN.test(entry.name)) {
|
|
2153
2295
|
notSynced.push(`canvas/${entry.name}${entry.isDirectory() ? "/" : ""}`);
|
|
@@ -2161,11 +2303,11 @@ function scanLocal(paths) {
|
|
|
2161
2303
|
return { units, notSynced: notSynced.sort() };
|
|
2162
2304
|
}
|
|
2163
2305
|
function unitBuildDir(paths, key) {
|
|
2164
|
-
return
|
|
2306
|
+
return path13.join(paths.cache, "cloud", "build", ...key.split("/"));
|
|
2165
2307
|
}
|
|
2166
2308
|
async function buildUnit(paths, key, out = unitBuildDir(paths, key)) {
|
|
2167
2309
|
const id2 = unitCanvasId(key);
|
|
2168
|
-
|
|
2310
|
+
fs13.rmSync(out, { recursive: true, force: true });
|
|
2169
2311
|
return buildSite(paths, out, {
|
|
2170
2312
|
canvases: id2 === null ? [] : [id2],
|
|
2171
2313
|
includeSystem: id2 === null,
|
|
@@ -2296,7 +2438,7 @@ var CloudClient = class {
|
|
|
2296
2438
|
}
|
|
2297
2439
|
host;
|
|
2298
2440
|
token;
|
|
2299
|
-
async send(method,
|
|
2441
|
+
async send(method, path27, init) {
|
|
2300
2442
|
const headers = {
|
|
2301
2443
|
"User-Agent": `foss-design/${packageVersion()} (${os.platform()})`,
|
|
2302
2444
|
Accept: "application/json"
|
|
@@ -2304,7 +2446,7 @@ var CloudClient = class {
|
|
|
2304
2446
|
if (this.token) headers.Authorization = `Bearer ${this.token}`;
|
|
2305
2447
|
if (init.type) headers["Content-Type"] = init.type;
|
|
2306
2448
|
try {
|
|
2307
|
-
return await fetch(`${this.host}/api${
|
|
2449
|
+
return await fetch(`${this.host}/api${path27}`, {
|
|
2308
2450
|
method,
|
|
2309
2451
|
headers,
|
|
2310
2452
|
body: init.body,
|
|
@@ -2325,8 +2467,8 @@ var CloudClient = class {
|
|
|
2325
2467
|
throw errorFor(this.host, response.status, body, response.headers.get("retry-after"));
|
|
2326
2468
|
}
|
|
2327
2469
|
/** JSON in, JSON out; any non-2xx answer throws a CloudError. */
|
|
2328
|
-
async request(method,
|
|
2329
|
-
const response = await this.send(method,
|
|
2470
|
+
async request(method, path27, body) {
|
|
2471
|
+
const response = await this.send(method, path27, {
|
|
2330
2472
|
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
2331
2473
|
type: body === void 0 ? void 0 : "application/json",
|
|
2332
2474
|
timeout: JSON_TIMEOUT
|
|
@@ -2338,7 +2480,7 @@ var CloudClient = class {
|
|
|
2338
2480
|
try {
|
|
2339
2481
|
return JSON.parse(text2);
|
|
2340
2482
|
} catch {
|
|
2341
|
-
throw new CloudError(`${this.host} answered ${method} ${
|
|
2483
|
+
throw new CloudError(`${this.host} answered ${method} ${path27} with something other than JSON`, 0, "bad_response");
|
|
2342
2484
|
}
|
|
2343
2485
|
}
|
|
2344
2486
|
/** POST /device/code. */
|
|
@@ -2472,9 +2614,9 @@ var CloudClient = class {
|
|
|
2472
2614
|
};
|
|
2473
2615
|
|
|
2474
2616
|
// src/cloud/credentials.ts
|
|
2475
|
-
import
|
|
2617
|
+
import fs14 from "fs";
|
|
2476
2618
|
import os2 from "os";
|
|
2477
|
-
import
|
|
2619
|
+
import path14 from "path";
|
|
2478
2620
|
var DEFAULT_CLOUD = "https://app.fossdesign.dev";
|
|
2479
2621
|
function normalizeHost(value) {
|
|
2480
2622
|
let url;
|
|
@@ -2491,14 +2633,14 @@ function cloudHost() {
|
|
|
2491
2633
|
return normalizeHost(process.env.FOSS_DESIGN_CLOUD || DEFAULT_CLOUD);
|
|
2492
2634
|
}
|
|
2493
2635
|
function credentialsFile() {
|
|
2494
|
-
const base = process.platform === "win32" && process.env.APPDATA ? process.env.APPDATA : process.env.XDG_CONFIG_HOME ||
|
|
2495
|
-
return
|
|
2636
|
+
const base = process.platform === "win32" && process.env.APPDATA ? process.env.APPDATA : process.env.XDG_CONFIG_HOME || path14.join(os2.homedir(), ".config");
|
|
2637
|
+
return path14.join(base, "foss-design", "credentials.json");
|
|
2496
2638
|
}
|
|
2497
2639
|
function readAll() {
|
|
2498
2640
|
const file = credentialsFile();
|
|
2499
|
-
if (!
|
|
2641
|
+
if (!fs14.existsSync(file)) return {};
|
|
2500
2642
|
try {
|
|
2501
|
-
const data = JSON.parse(
|
|
2643
|
+
const data = JSON.parse(fs14.readFileSync(file, "utf8"));
|
|
2502
2644
|
return data && typeof data === "object" && !Array.isArray(data) ? data : {};
|
|
2503
2645
|
} catch (error) {
|
|
2504
2646
|
throw new CliError(`${file} is not valid JSON (${error.message}); fix or delete it`);
|
|
@@ -2506,12 +2648,12 @@ function readAll() {
|
|
|
2506
2648
|
}
|
|
2507
2649
|
function writeAll(data) {
|
|
2508
2650
|
const file = credentialsFile();
|
|
2509
|
-
|
|
2651
|
+
fs14.mkdirSync(path14.dirname(file), { recursive: true, mode: 448 });
|
|
2510
2652
|
const temp = `${file}.${process.pid}.tmp`;
|
|
2511
|
-
|
|
2653
|
+
fs14.writeFileSync(temp, `${JSON.stringify(data, null, 2)}
|
|
2512
2654
|
`, { mode: 384 });
|
|
2513
|
-
|
|
2514
|
-
|
|
2655
|
+
fs14.chmodSync(temp, 384);
|
|
2656
|
+
fs14.renameSync(temp, file);
|
|
2515
2657
|
}
|
|
2516
2658
|
function getCredential(host) {
|
|
2517
2659
|
const entry = readAll()[host];
|
|
@@ -2534,17 +2676,17 @@ function requireCredential(host) {
|
|
|
2534
2676
|
}
|
|
2535
2677
|
|
|
2536
2678
|
// src/cloud/state.ts
|
|
2537
|
-
import
|
|
2538
|
-
import
|
|
2539
|
-
var linkFile = (paths) =>
|
|
2540
|
-
var cloudCache = (paths) =>
|
|
2541
|
-
var incomingDir = (paths, key) =>
|
|
2679
|
+
import fs15 from "fs";
|
|
2680
|
+
import path15 from "path";
|
|
2681
|
+
var linkFile = (paths) => path15.join(paths.design, "cloud.json");
|
|
2682
|
+
var cloudCache = (paths) => path15.join(paths.cache, "cloud");
|
|
2683
|
+
var incomingDir = (paths, key) => path15.join(cloudCache(paths), "incoming", ...key.split("/"));
|
|
2542
2684
|
function readLink(paths) {
|
|
2543
2685
|
const file = linkFile(paths);
|
|
2544
|
-
if (!
|
|
2686
|
+
if (!fs15.existsSync(file)) return null;
|
|
2545
2687
|
let data;
|
|
2546
2688
|
try {
|
|
2547
|
-
data = JSON.parse(
|
|
2689
|
+
data = JSON.parse(fs15.readFileSync(file, "utf8"));
|
|
2548
2690
|
} catch (error) {
|
|
2549
2691
|
throw new CliError(`${file} is not valid JSON (${error.message}); run \`design link <project>\` again`);
|
|
2550
2692
|
}
|
|
@@ -2574,9 +2716,9 @@ function writeLink(paths, link) {
|
|
|
2574
2716
|
units: sorted(link.units),
|
|
2575
2717
|
conflicts: sorted(link.conflicts)
|
|
2576
2718
|
};
|
|
2577
|
-
|
|
2719
|
+
fs15.writeFileSync(temp, `${JSON.stringify(data, null, 2)}
|
|
2578
2720
|
`);
|
|
2579
|
-
|
|
2721
|
+
fs15.renameSync(temp, file);
|
|
2580
2722
|
}
|
|
2581
2723
|
var hashes = (manifest) => Object.fromEntries(Object.entries(manifest).map(([file, entry]) => [file, entry.hash]));
|
|
2582
2724
|
function diffFiles(a, b) {
|
|
@@ -2654,8 +2796,8 @@ var byUnit = (a, b) => a === SYSTEM_UNIT ? -1 : b === SYSTEM_UNIT ? 1 : a < b ?
|
|
|
2654
2796
|
var plural = (count, word, many = `${word}s`) => `${count} ${count === 1 ? word : many}`;
|
|
2655
2797
|
|
|
2656
2798
|
// src/cloud/pull.ts
|
|
2657
|
-
import
|
|
2658
|
-
import
|
|
2799
|
+
import fs16 from "fs";
|
|
2800
|
+
import path16 from "path";
|
|
2659
2801
|
|
|
2660
2802
|
// src/capture/frames.ts
|
|
2661
2803
|
var AUTO_VIEWPORT_HEIGHT = 900;
|
|
@@ -2738,34 +2880,34 @@ function checkManifest(key, manifest) {
|
|
|
2738
2880
|
}
|
|
2739
2881
|
}
|
|
2740
2882
|
function writeInside(root, rel, data) {
|
|
2741
|
-
const file =
|
|
2883
|
+
const file = path16.join(root, rel);
|
|
2742
2884
|
if (!isInside(root, file)) throw new CliError(`Refusing to write outside ${root}: ${rel}`);
|
|
2743
|
-
|
|
2744
|
-
if (!isInside(
|
|
2885
|
+
fs16.mkdirSync(path16.dirname(file), { recursive: true });
|
|
2886
|
+
if (!isInside(fs16.realpathSync(root), fs16.realpathSync(path16.dirname(file))))
|
|
2745
2887
|
throw new CliError(`Refusing to write through a link out of ${root}: ${rel}`);
|
|
2746
|
-
if (
|
|
2747
|
-
|
|
2888
|
+
if (fs16.existsSync(file) && fs16.lstatSync(file).isSymbolicLink()) fs16.unlinkSync(file);
|
|
2889
|
+
fs16.writeFileSync(file, data);
|
|
2748
2890
|
}
|
|
2749
2891
|
function pruneEmpty(dir, stop) {
|
|
2750
2892
|
let current = dir;
|
|
2751
2893
|
while (isInside(stop, current) && current !== stop) {
|
|
2752
2894
|
try {
|
|
2753
|
-
if (
|
|
2754
|
-
|
|
2895
|
+
if (fs16.readdirSync(current).length) return;
|
|
2896
|
+
fs16.rmdirSync(current);
|
|
2755
2897
|
} catch {
|
|
2756
2898
|
return;
|
|
2757
2899
|
}
|
|
2758
|
-
current =
|
|
2900
|
+
current = path16.dirname(current);
|
|
2759
2901
|
}
|
|
2760
2902
|
}
|
|
2761
2903
|
function applyUnit(paths, key, local, target, blobs) {
|
|
2762
2904
|
const counts = { added: 0, changed: 0, removed: 0 };
|
|
2763
|
-
const stop = key === SYSTEM_UNIT ? paths.design :
|
|
2905
|
+
const stop = key === SYSTEM_UNIT ? paths.design : path16.dirname(unitRoot(paths, key));
|
|
2764
2906
|
for (const rel of Object.keys(local)) {
|
|
2765
2907
|
if (rel in target) continue;
|
|
2766
|
-
const file =
|
|
2767
|
-
|
|
2768
|
-
pruneEmpty(
|
|
2908
|
+
const file = path16.join(paths.design, rel);
|
|
2909
|
+
fs16.rmSync(file, { force: true });
|
|
2910
|
+
pruneEmpty(path16.dirname(file), stop);
|
|
2769
2911
|
counts.removed++;
|
|
2770
2912
|
}
|
|
2771
2913
|
for (const [rel, entry] of Object.entries(target)) {
|
|
@@ -2828,7 +2970,7 @@ async function pull(ctx, options) {
|
|
|
2828
2970
|
} else if (!s.ahead) {
|
|
2829
2971
|
if (s.conflictRev !== void 0) {
|
|
2830
2972
|
delete link.conflicts[key];
|
|
2831
|
-
|
|
2973
|
+
fs16.rmSync(incomingDir(paths, key), { recursive: true, force: true });
|
|
2832
2974
|
}
|
|
2833
2975
|
report.units.push({ ...reportFor("up_to_date"), rev: s.baseRev });
|
|
2834
2976
|
} else if (!s.changed.length) {
|
|
@@ -2858,14 +3000,14 @@ async function pull(ctx, options) {
|
|
|
2858
3000
|
headRev: unit.headRev,
|
|
2859
3001
|
incoming: incomingDir(paths, key)
|
|
2860
3002
|
};
|
|
2861
|
-
entry.message = `changed here and in the cloud; the cloud version (r${unit.headRev}) is in ${
|
|
2862
|
-
if (s.conflictRev === unit.headRev &&
|
|
3003
|
+
entry.message = `changed here and in the cloud; the cloud version (r${unit.headRev}) is in ${path16.relative(paths.root, entry.incoming)}`;
|
|
3004
|
+
if (s.conflictRev === unit.headRev && fs16.existsSync(entry.incoming)) report.units.push(entry);
|
|
2863
3005
|
else plans.push({ key, kind: "incoming", remote: unit, report: entry });
|
|
2864
3006
|
}
|
|
2865
3007
|
}
|
|
2866
3008
|
const byHash = /* @__PURE__ */ new Map();
|
|
2867
3009
|
for (const unit of scan.units.values())
|
|
2868
|
-
for (const [rel, entry] of Object.entries(unit.manifest)) byHash.set(entry.hash,
|
|
3010
|
+
for (const [rel, entry] of Object.entries(unit.manifest)) byHash.set(entry.hash, path16.join(paths.design, rel));
|
|
2869
3011
|
const needed = /* @__PURE__ */ new Set();
|
|
2870
3012
|
for (const plan of plans) {
|
|
2871
3013
|
if (!plan.remote) continue;
|
|
@@ -2876,7 +3018,7 @@ async function pull(ctx, options) {
|
|
|
2876
3018
|
const download = [];
|
|
2877
3019
|
for (const hash of needed) {
|
|
2878
3020
|
const file = byHash.get(hash);
|
|
2879
|
-
if (file) blobs.set(hash,
|
|
3021
|
+
if (file) blobs.set(hash, fs16.readFileSync(file));
|
|
2880
3022
|
else download.push(hash);
|
|
2881
3023
|
}
|
|
2882
3024
|
if (download.length) {
|
|
@@ -2902,11 +3044,11 @@ async function pull(ctx, options) {
|
|
|
2902
3044
|
if (plan.remote) link.units[key] = { rev: plan.remote.headRev, files: hashes(plan.remote.manifest) };
|
|
2903
3045
|
else delete link.units[key];
|
|
2904
3046
|
delete link.conflicts[key];
|
|
2905
|
-
|
|
3047
|
+
fs16.rmSync(incomingDir(paths, key), { recursive: true, force: true });
|
|
2906
3048
|
} else {
|
|
2907
3049
|
const dir = incomingDir(paths, key);
|
|
2908
|
-
|
|
2909
|
-
|
|
3050
|
+
fs16.rmSync(dir, { recursive: true, force: true });
|
|
3051
|
+
fs16.mkdirSync(dir, { recursive: true });
|
|
2910
3052
|
for (const [rel, file] of Object.entries(plan.remote.manifest))
|
|
2911
3053
|
writeInside(dir, unitRelative(key, rel), blobs.get(file.hash));
|
|
2912
3054
|
link.conflicts[key] = plan.remote.headRev;
|
|
@@ -2927,8 +3069,8 @@ async function pull(ctx, options) {
|
|
|
2927
3069
|
}
|
|
2928
3070
|
|
|
2929
3071
|
// src/cloud/push.ts
|
|
2930
|
-
import
|
|
2931
|
-
import
|
|
3072
|
+
import fs17 from "fs";
|
|
3073
|
+
import path17 from "path";
|
|
2932
3074
|
function blockedReport(status, message) {
|
|
2933
3075
|
return {
|
|
2934
3076
|
unit: status.key,
|
|
@@ -3034,7 +3176,7 @@ async function push(ctx, options) {
|
|
|
3034
3176
|
for (const key of toPush) {
|
|
3035
3177
|
const s = status(key);
|
|
3036
3178
|
if (s.conflictRev !== void 0) {
|
|
3037
|
-
const dir =
|
|
3179
|
+
const dir = path17.relative(paths.root, incomingDir(paths, key));
|
|
3038
3180
|
blocked.push(
|
|
3039
3181
|
blockedReport(s, `pull left the cloud version in ${dir}; merge it, then \`design push --resolved ${key}\``)
|
|
3040
3182
|
);
|
|
@@ -3078,7 +3220,7 @@ async function push(ctx, options) {
|
|
|
3078
3220
|
const files = /* @__PURE__ */ new Map();
|
|
3079
3221
|
const addFiles = (manifest, dir) => {
|
|
3080
3222
|
for (const [rel, entry] of Object.entries(manifest))
|
|
3081
|
-
if (!files.has(entry.hash)) files.set(entry.hash,
|
|
3223
|
+
if (!files.has(entry.hash)) files.set(entry.hash, path17.join(dir, rel));
|
|
3082
3224
|
};
|
|
3083
3225
|
for (const key of pushing) {
|
|
3084
3226
|
const id2 = unitCanvasId(key);
|
|
@@ -3110,7 +3252,7 @@ async function push(ctx, options) {
|
|
|
3110
3252
|
if (missing.length) {
|
|
3111
3253
|
const unknown = missing.find((hash) => !files.has(hash));
|
|
3112
3254
|
if (unknown) throw new CliError(`The cloud asked for ${unknown.slice(0, 12)}, which is not part of this push`);
|
|
3113
|
-
const bytes = missing.reduce((sum, hash) => sum +
|
|
3255
|
+
const bytes = missing.reduce((sum, hash) => sum + fs17.statSync(files.get(hash)).size, 0);
|
|
3114
3256
|
const bar = ctx.progress();
|
|
3115
3257
|
const started = Date.now();
|
|
3116
3258
|
let sentFiles = 0;
|
|
@@ -3119,12 +3261,12 @@ async function push(ctx, options) {
|
|
|
3119
3261
|
bar.update(line());
|
|
3120
3262
|
await mapLimit(missing, TRANSFERS, async (hash) => {
|
|
3121
3263
|
const file = files.get(hash);
|
|
3122
|
-
const data =
|
|
3264
|
+
const data = fs17.readFileSync(file);
|
|
3123
3265
|
try {
|
|
3124
3266
|
await client.putBlob(link.project, hash, data);
|
|
3125
3267
|
} catch (error) {
|
|
3126
3268
|
if (error instanceof CloudError && error.status === 413)
|
|
3127
|
-
throw new CliError(`${
|
|
3269
|
+
throw new CliError(`${path17.relative(process.cwd(), file)} (${megabytes(data.length)}): ${error.message}`);
|
|
3128
3270
|
throw error;
|
|
3129
3271
|
}
|
|
3130
3272
|
sentFiles++;
|
|
@@ -3146,7 +3288,7 @@ async function push(ctx, options) {
|
|
|
3146
3288
|
url: webUrl(link, unit.key)
|
|
3147
3289
|
});
|
|
3148
3290
|
}
|
|
3149
|
-
for (const key of resolved)
|
|
3291
|
+
for (const key of resolved) fs17.rmSync(incomingDir(paths, key), { recursive: true, force: true });
|
|
3150
3292
|
writeLink(paths, link);
|
|
3151
3293
|
report.units.sort((a, b) => byUnit(a.unit, b.unit));
|
|
3152
3294
|
return report;
|
|
@@ -3156,8 +3298,9 @@ function refuseLegacyGo(paths, keys) {
|
|
|
3156
3298
|
const hits = /* @__PURE__ */ new Set();
|
|
3157
3299
|
for (const key of keys) {
|
|
3158
3300
|
if (!key.startsWith("canvas/")) continue;
|
|
3159
|
-
|
|
3160
|
-
|
|
3301
|
+
const id2 = key.slice("canvas/".length);
|
|
3302
|
+
for (const file of canvasCodeFiles(path17.join(paths.canvases, id2), project.canvas(id2)?.screens ?? [])) {
|
|
3303
|
+
for (const line of legacyGoLines(file)) hits.add(`${relToRoot(paths, file)}:${line}`);
|
|
3161
3304
|
}
|
|
3162
3305
|
}
|
|
3163
3306
|
if (!hits.size) return;
|
|
@@ -3477,7 +3620,7 @@ async function runMe(paths, options) {
|
|
|
3477
3620
|
}
|
|
3478
3621
|
|
|
3479
3622
|
// src/capture/chrome.ts
|
|
3480
|
-
import
|
|
3623
|
+
import fs18 from "fs";
|
|
3481
3624
|
var CANDIDATES = {
|
|
3482
3625
|
darwin: [
|
|
3483
3626
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
@@ -3501,8 +3644,8 @@ var CANDIDATES = {
|
|
|
3501
3644
|
};
|
|
3502
3645
|
function findChrome() {
|
|
3503
3646
|
const configured = process.env.DESIGN_CHROME;
|
|
3504
|
-
if (configured &&
|
|
3505
|
-
for (const candidate of CANDIDATES[process.platform] ?? []) if (
|
|
3647
|
+
if (configured && fs18.existsSync(configured)) return configured;
|
|
3648
|
+
for (const candidate of CANDIDATES[process.platform] ?? []) if (fs18.existsSync(candidate)) return candidate;
|
|
3506
3649
|
return null;
|
|
3507
3650
|
}
|
|
3508
3651
|
async function launchChrome() {
|
|
@@ -3515,23 +3658,42 @@ async function launchChrome() {
|
|
|
3515
3658
|
const { chromium } = await import("playwright-core");
|
|
3516
3659
|
return chromium.launch({ executablePath, headless: true, args: ["--hide-scrollbars", "--force-color-profile=srgb"] });
|
|
3517
3660
|
}
|
|
3661
|
+
var OUTDATED_DEP = /Outdated Optimize Dep/;
|
|
3518
3662
|
async function openFrame(page, url, options = {}) {
|
|
3519
|
-
|
|
3520
|
-
|
|
3663
|
+
let errors = [];
|
|
3664
|
+
let outdated = () => {
|
|
3665
|
+
};
|
|
3666
|
+
const record2 = (text2) => {
|
|
3667
|
+
errors.push(text2);
|
|
3668
|
+
if (OUTDATED_DEP.test(text2)) outdated();
|
|
3669
|
+
};
|
|
3670
|
+
page.on("pageerror", (error) => record2(error.message));
|
|
3521
3671
|
page.on("console", (message) => {
|
|
3522
|
-
if (message.type()
|
|
3672
|
+
if (message.type() !== "error") return;
|
|
3673
|
+
let text2 = message.text().replace(/^(?:\s*%[osdifcO])+\s*/, "");
|
|
3674
|
+
const where = message.location().url;
|
|
3675
|
+
if (/^Failed to load resource/.test(text2) && where && !text2.includes(where)) text2 += ` (${where})`;
|
|
3676
|
+
record2(text2);
|
|
3523
3677
|
});
|
|
3524
|
-
|
|
3525
|
-
let
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3678
|
+
let ready = false;
|
|
3679
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
3680
|
+
errors = [];
|
|
3681
|
+
ready = options.waitForReady === false;
|
|
3682
|
+
const turnedAway = new Promise((resolve) => {
|
|
3683
|
+
outdated = () => resolve("outdated");
|
|
3684
|
+
});
|
|
3685
|
+
await page.goto(url, { waitUntil: "load", timeout: options.timeoutMs ?? 3e4 });
|
|
3686
|
+
if (!ready) {
|
|
3687
|
+
const signal = page.waitForFunction(() => window.__DESIGN_READY__ === true, null, {
|
|
3529
3688
|
timeout: options.timeoutMs ?? 2e4
|
|
3689
|
+
}).then(() => "ready");
|
|
3690
|
+
signal.catch(() => {
|
|
3530
3691
|
});
|
|
3531
|
-
ready =
|
|
3532
|
-
} catch {
|
|
3692
|
+
ready = await Promise.race([signal, turnedAway]).catch(() => null) === "ready";
|
|
3533
3693
|
}
|
|
3694
|
+
if (!errors.some((error) => OUTDATED_DEP.test(error))) break;
|
|
3534
3695
|
}
|
|
3696
|
+
const held = ready ? 0 : await page.evaluate(() => window.__DESIGN_HOLDS__ ?? 0).catch(() => 0);
|
|
3535
3697
|
await page.waitForTimeout(options.settleMs ?? 700);
|
|
3536
3698
|
const runtimeErrors = await page.evaluate(() => window.__DESIGN_ERRORS__ ?? []).catch(() => []);
|
|
3537
3699
|
for (const error of runtimeErrors) if (!errors.includes(error)) errors.push(error);
|
|
@@ -3543,30 +3705,30 @@ async function openFrame(page, url, options = {}) {
|
|
|
3543
3705
|
const unique2 = errors.filter(
|
|
3544
3706
|
(error, index) => error && !errors.some((other, j) => j !== index && other && other.length < error.length && error.includes(other))
|
|
3545
3707
|
);
|
|
3546
|
-
return { errors: [...new Set(unique2)], ready };
|
|
3708
|
+
return { errors: [...new Set(unique2)], ready, ...held ? { held } : {} };
|
|
3547
3709
|
}
|
|
3548
3710
|
|
|
3549
3711
|
// src/server/state.ts
|
|
3550
|
-
import
|
|
3551
|
-
import
|
|
3552
|
-
var stateFile = (paths) =>
|
|
3553
|
-
var logFile = (paths) =>
|
|
3712
|
+
import fs19 from "fs";
|
|
3713
|
+
import path18 from "path";
|
|
3714
|
+
var stateFile = (paths) => path18.join(paths.cache, "server.json");
|
|
3715
|
+
var logFile = (paths) => path18.join(paths.cache, "server.log");
|
|
3554
3716
|
function readState(paths) {
|
|
3555
3717
|
try {
|
|
3556
|
-
return JSON.parse(
|
|
3718
|
+
return JSON.parse(fs19.readFileSync(stateFile(paths), "utf8"));
|
|
3557
3719
|
} catch {
|
|
3558
3720
|
return null;
|
|
3559
3721
|
}
|
|
3560
3722
|
}
|
|
3561
3723
|
function writeState(paths, state) {
|
|
3562
|
-
|
|
3563
|
-
|
|
3724
|
+
fs19.mkdirSync(paths.cache, { recursive: true });
|
|
3725
|
+
fs19.writeFileSync(stateFile(paths), `${JSON.stringify(state, null, 2)}
|
|
3564
3726
|
`);
|
|
3565
3727
|
}
|
|
3566
3728
|
function clearState(paths, pid) {
|
|
3567
3729
|
const state = readState(paths);
|
|
3568
3730
|
if (pid !== void 0 && state && state.pid !== pid) return;
|
|
3569
|
-
|
|
3731
|
+
fs19.rmSync(stateFile(paths), { force: true });
|
|
3570
3732
|
}
|
|
3571
3733
|
function pidAlive(pid) {
|
|
3572
3734
|
try {
|
|
@@ -3591,18 +3753,18 @@ async function liveServer(paths) {
|
|
|
3591
3753
|
|
|
3592
3754
|
// src/cli/commands/preview.ts
|
|
3593
3755
|
import { spawn } from "child_process";
|
|
3594
|
-
import
|
|
3756
|
+
import fs23 from "fs";
|
|
3595
3757
|
|
|
3596
3758
|
// src/server/index.ts
|
|
3597
|
-
import
|
|
3759
|
+
import fs22 from "fs";
|
|
3598
3760
|
import http from "http";
|
|
3599
|
-
import
|
|
3761
|
+
import path21 from "path";
|
|
3600
3762
|
import sirv from "sirv";
|
|
3601
3763
|
import { createServer, searchForWorkspaceRoot } from "vite";
|
|
3602
3764
|
|
|
3603
3765
|
// src/server/api.ts
|
|
3604
|
-
import
|
|
3605
|
-
import
|
|
3766
|
+
import fs20 from "fs";
|
|
3767
|
+
import path19 from "path";
|
|
3606
3768
|
function sendJson(res, status, body) {
|
|
3607
3769
|
res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
|
|
3608
3770
|
res.end(JSON.stringify(body));
|
|
@@ -3653,17 +3815,17 @@ async function handleApi(ctx, req, res, url) {
|
|
|
3653
3815
|
}
|
|
3654
3816
|
if (route2 === "/source") {
|
|
3655
3817
|
const rel = url.searchParams.get("path") ?? "";
|
|
3656
|
-
const file =
|
|
3657
|
-
if (!rel || !isInside(project.paths.root, file) || !
|
|
3818
|
+
const file = path19.resolve(project.paths.root, rel);
|
|
3819
|
+
if (!rel || !isInside(project.paths.root, file) || !fs20.existsSync(file) || fs20.statSync(file).isDirectory()) {
|
|
3658
3820
|
sendJson(res, 404, { error: "not found" });
|
|
3659
3821
|
return true;
|
|
3660
3822
|
}
|
|
3661
|
-
if (
|
|
3823
|
+
if (fs20.statSync(file).size > 2 * 1024 * 1024) {
|
|
3662
3824
|
sendJson(res, 413, { error: "file too large to show" });
|
|
3663
3825
|
return true;
|
|
3664
3826
|
}
|
|
3665
3827
|
res.writeHead(200, { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" });
|
|
3666
|
-
res.end(
|
|
3828
|
+
res.end(fs20.readFileSync(file, "utf8"));
|
|
3667
3829
|
return true;
|
|
3668
3830
|
}
|
|
3669
3831
|
const snapshotPost = /^\/snapshots\/([^/]+)\/([^/]+)$/.exec(route2);
|
|
@@ -3693,7 +3855,7 @@ async function handleApi(ctx, req, res, url) {
|
|
|
3693
3855
|
return true;
|
|
3694
3856
|
}
|
|
3695
3857
|
res.writeHead(200, { "content-type": "image/png", "cache-control": "public, max-age=31536000, immutable" });
|
|
3696
|
-
|
|
3858
|
+
fs20.createReadStream(file).pipe(res);
|
|
3697
3859
|
return true;
|
|
3698
3860
|
}
|
|
3699
3861
|
return false;
|
|
@@ -3733,8 +3895,8 @@ var EventHub = class {
|
|
|
3733
3895
|
};
|
|
3734
3896
|
|
|
3735
3897
|
// src/server/snapshots.ts
|
|
3736
|
-
import
|
|
3737
|
-
import
|
|
3898
|
+
import fs21 from "fs";
|
|
3899
|
+
import path20 from "path";
|
|
3738
3900
|
var SAFE = /^@?[a-z0-9][a-z0-9_-]*$/i;
|
|
3739
3901
|
function isSafeSegment(value) {
|
|
3740
3902
|
return SAFE.test(value);
|
|
@@ -3748,14 +3910,14 @@ var SnapshotStore = class {
|
|
|
3748
3910
|
urlBase;
|
|
3749
3911
|
meta = /* @__PURE__ */ new Map();
|
|
3750
3912
|
metaFile(canvas) {
|
|
3751
|
-
return
|
|
3913
|
+
return path20.join(this.dir, canvas, "meta.json");
|
|
3752
3914
|
}
|
|
3753
3915
|
load(canvas) {
|
|
3754
3916
|
const cached = this.meta.get(canvas);
|
|
3755
3917
|
if (cached) return cached;
|
|
3756
3918
|
let data = {};
|
|
3757
3919
|
try {
|
|
3758
|
-
data = JSON.parse(
|
|
3920
|
+
data = JSON.parse(fs21.readFileSync(this.metaFile(canvas), "utf8"));
|
|
3759
3921
|
} catch {
|
|
3760
3922
|
}
|
|
3761
3923
|
this.meta.set(canvas, data);
|
|
@@ -3781,21 +3943,21 @@ var SnapshotStore = class {
|
|
|
3781
3943
|
};
|
|
3782
3944
|
save(canvas, id2, theme2, png, height) {
|
|
3783
3945
|
if (!isSafeSegment(canvas) || !isSafeSegment(id2)) throw new Error("bad snapshot key");
|
|
3784
|
-
const dir =
|
|
3785
|
-
|
|
3786
|
-
|
|
3946
|
+
const dir = path20.join(this.dir, canvas);
|
|
3947
|
+
fs21.mkdirSync(dir, { recursive: true });
|
|
3948
|
+
fs21.writeFileSync(path20.join(dir, `${id2}.${theme2}.png`), png);
|
|
3787
3949
|
const data = this.load(canvas);
|
|
3788
3950
|
const version = Date.now();
|
|
3789
3951
|
data[id2] = { ...data[id2], [theme2]: version, ...height ? { height } : {} };
|
|
3790
|
-
|
|
3952
|
+
fs21.writeFileSync(this.metaFile(canvas), JSON.stringify(data));
|
|
3791
3953
|
return { url: this.url(canvas, id2, theme2, version) };
|
|
3792
3954
|
}
|
|
3793
3955
|
/** Absolute path of `<id>.<theme>.png`, or of its thumbnail for `<id>.<theme>.thumb.png` (made on first use); null when missing. */
|
|
3794
3956
|
file(canvas, name) {
|
|
3795
3957
|
const match = /^(@?[a-z0-9][a-z0-9_-]*)\.(light|dark)(\.thumb)?\.png$/i.exec(name);
|
|
3796
3958
|
if (!isSafeSegment(canvas) || !match) return null;
|
|
3797
|
-
const file =
|
|
3798
|
-
if (!
|
|
3959
|
+
const file = path20.join(this.dir, canvas, `${match[1]}.${match[2]}.png`);
|
|
3960
|
+
if (!fs21.existsSync(file)) return null;
|
|
3799
3961
|
return match[3] ? ensureThumb(file) : file;
|
|
3800
3962
|
}
|
|
3801
3963
|
};
|
|
@@ -3834,8 +3996,8 @@ function notFoundPage(res, title, text2) {
|
|
|
3834
3996
|
}
|
|
3835
3997
|
async function startDevServer(options) {
|
|
3836
3998
|
const paths = designPaths(options.root);
|
|
3837
|
-
if (!
|
|
3838
|
-
|
|
3999
|
+
if (!fs22.existsSync(paths.design)) throw new Error(`No .design folder in ${options.root}; run \`design init\``);
|
|
4000
|
+
fs22.mkdirSync(paths.cache, { recursive: true });
|
|
3839
4001
|
const snapshots = new SnapshotStore(paths.snapshots);
|
|
3840
4002
|
const project = new DesignProject(paths, DEV_URLS, snapshots.lookup);
|
|
3841
4003
|
const entries = new Entries(project);
|
|
@@ -3843,6 +4005,7 @@ async function startDevServer(options) {
|
|
|
3843
4005
|
const links = linkShippedPackages(paths);
|
|
3844
4006
|
const events = new EventHub();
|
|
3845
4007
|
const httpServer = http.createServer();
|
|
4008
|
+
dropStaleDepCache(paths);
|
|
3846
4009
|
const base = baseConfig(project);
|
|
3847
4010
|
const vite = await createServer({
|
|
3848
4011
|
...base,
|
|
@@ -3861,19 +4024,28 @@ async function startDevServer(options) {
|
|
|
3861
4024
|
}
|
|
3862
4025
|
},
|
|
3863
4026
|
optimizeDeps: {
|
|
3864
|
-
entries: entries.files().map((file) => toPosix(
|
|
4027
|
+
entries: entries.files().map((file) => toPosix(path21.relative(paths.design, file)))
|
|
3865
4028
|
}
|
|
3866
4029
|
});
|
|
3867
|
-
const
|
|
3868
|
-
const
|
|
4030
|
+
const publicDir = project.publicDir();
|
|
4031
|
+
const servePublic = publicDir ? sirv(publicDir, { dev: true }) : null;
|
|
4032
|
+
const publicRel = (encoded) => {
|
|
4033
|
+
try {
|
|
4034
|
+
return decodeURIComponent(encoded);
|
|
4035
|
+
} catch {
|
|
4036
|
+
return null;
|
|
4037
|
+
}
|
|
4038
|
+
};
|
|
4039
|
+
const viewer = fs22.existsSync(PKG.viewer) ? sirv(PKG.viewer, { dev: true, etag: true }) : null;
|
|
4040
|
+
const viewerIndex = path21.join(PKG.viewer, "index.html");
|
|
3869
4041
|
const serveViewerIndex = (res) => {
|
|
3870
|
-
const html =
|
|
4042
|
+
const html = fs22.readFileSync(viewerIndex, "utf8").replace(/<head>/i, '<head>\n<base href="/">');
|
|
3871
4043
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
3872
4044
|
res.end(html);
|
|
3873
4045
|
};
|
|
3874
4046
|
const ctx = { project, events, snapshots };
|
|
3875
4047
|
const systemCssUrl = `/${relToDesign(paths, entries.systemCssFile())}`;
|
|
3876
|
-
const bootUrl = `/${relToDesign(paths,
|
|
4048
|
+
const bootUrl = `/${relToDesign(paths, path21.join(entries.dir, HTML_BOOT))}`;
|
|
3877
4049
|
const frameHead = (source, extra = {}) => {
|
|
3878
4050
|
const canvasTheme = source.canvas === SYSTEM_CANVAS ? void 0 : project.canvas(source.canvas)?.doc.theme;
|
|
3879
4051
|
return {
|
|
@@ -3882,6 +4054,8 @@ async function startDevServer(options) {
|
|
|
3882
4054
|
fonts: project.system()?.doc.fonts ?? [],
|
|
3883
4055
|
snapshots: source.canvas !== SYSTEM_CANVAS,
|
|
3884
4056
|
autoHeight: source.autoHeight ?? false,
|
|
4057
|
+
// Vite serves the public folder under the frames' base.
|
|
4058
|
+
public: { base: "/_fs/", files: project.publicFiles() },
|
|
3885
4059
|
...extra
|
|
3886
4060
|
};
|
|
3887
4061
|
};
|
|
@@ -3892,7 +4066,7 @@ async function startDevServer(options) {
|
|
|
3892
4066
|
notFoundPage(res, "Screen not found", `No screen "${id2}" in "${canvas}". Check canvas.json.`);
|
|
3893
4067
|
return;
|
|
3894
4068
|
}
|
|
3895
|
-
if (!
|
|
4069
|
+
if (!fs22.existsSync(entries.entryFile(source))) entries.sync();
|
|
3896
4070
|
const entryUrl = `/${relToDesign(paths, entries.entryFile(source))}`;
|
|
3897
4071
|
const html = await vite.transformIndexHtml(url2.pathname + url2.search, moduleShell(frameHead(source), entryUrl));
|
|
3898
4072
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
@@ -3901,8 +4075,8 @@ async function startDevServer(options) {
|
|
|
3901
4075
|
};
|
|
3902
4076
|
const serveHtmlScreen = async (res, url2) => {
|
|
3903
4077
|
const rel = decodeURIComponent(url2.pathname.slice("/_fs/".length));
|
|
3904
|
-
const file =
|
|
3905
|
-
if (!isInside(paths.design, file) || !
|
|
4078
|
+
const file = path21.resolve(paths.design, rel);
|
|
4079
|
+
if (!isInside(paths.design, file) || !fs22.existsSync(file)) {
|
|
3906
4080
|
notFoundPage(res, "Screen not found", `No file .design/${rel}`);
|
|
3907
4081
|
return;
|
|
3908
4082
|
}
|
|
@@ -3912,13 +4086,13 @@ async function startDevServer(options) {
|
|
|
3912
4086
|
key: `${canvas}/${id2}`,
|
|
3913
4087
|
canvas,
|
|
3914
4088
|
id: id2,
|
|
3915
|
-
title:
|
|
4089
|
+
title: path21.basename(file),
|
|
3916
4090
|
file,
|
|
3917
4091
|
format: "html",
|
|
3918
4092
|
props: {},
|
|
3919
4093
|
system: true
|
|
3920
4094
|
};
|
|
3921
|
-
const raw =
|
|
4095
|
+
const raw = fs22.readFileSync(file, "utf8");
|
|
3922
4096
|
const withSystem = source.system && !optsOutOfSystem(raw);
|
|
3923
4097
|
const html = injectIntoHtml(
|
|
3924
4098
|
raw,
|
|
@@ -3950,17 +4124,24 @@ async function startDevServer(options) {
|
|
|
3950
4124
|
return;
|
|
3951
4125
|
}
|
|
3952
4126
|
if (url2.pathname.startsWith("/_fs/")) {
|
|
4127
|
+
const rel = publicRel(url2.pathname.slice("/_fs/".length));
|
|
4128
|
+
if (publicDir && rel !== null && !project.isPublicFile(rel) && fs22.existsSync(path21.join(publicDir, rel))) {
|
|
4129
|
+
notFoundPage(res, "Not found", url2.pathname);
|
|
4130
|
+
return;
|
|
4131
|
+
}
|
|
3953
4132
|
vite.middlewares(req, res, () => notFoundPage(res, "Not found", url2.pathname));
|
|
3954
4133
|
return;
|
|
3955
4134
|
}
|
|
3956
4135
|
if (viewer) {
|
|
4136
|
+
const notViewer = () => {
|
|
4137
|
+
const rel = publicRel(url2.pathname.slice(1));
|
|
4138
|
+
if (servePublic && rel !== null && project.isPublicFile(rel))
|
|
4139
|
+
servePublic(req, res, () => notFoundPage(res, "Not found", url2.pathname));
|
|
4140
|
+
else if (path21.extname(url2.pathname)) notFoundPage(res, "Not found", url2.pathname);
|
|
4141
|
+
else serveViewerIndex(res);
|
|
4142
|
+
};
|
|
3957
4143
|
if (url2.pathname === "/" || url2.pathname === "/index.html") serveViewerIndex(res);
|
|
3958
|
-
else
|
|
3959
|
-
viewer(
|
|
3960
|
-
req,
|
|
3961
|
-
res,
|
|
3962
|
-
() => path20.extname(url2.pathname) ? notFoundPage(res, "Not found", url2.pathname) : serveViewerIndex(res)
|
|
3963
|
-
);
|
|
4144
|
+
else viewer(req, res, notViewer);
|
|
3964
4145
|
} else notFoundPage(res, "Viewer is not built", "Run `pnpm build` in the foss-design package.");
|
|
3965
4146
|
} catch (error) {
|
|
3966
4147
|
fail(error);
|
|
@@ -3983,7 +4164,8 @@ async function startDevServer(options) {
|
|
|
3983
4164
|
}, 60);
|
|
3984
4165
|
};
|
|
3985
4166
|
vite.watcher.on("all", (event, file) => {
|
|
3986
|
-
|
|
4167
|
+
if (publicDir && isInside(publicDir, file)) project.invalidatePublic();
|
|
4168
|
+
const rel = toPosix(path21.relative(paths.design, file));
|
|
3987
4169
|
if (rel.startsWith("..") || rel.startsWith(".cache/") || rel.startsWith("node_modules/")) return;
|
|
3988
4170
|
if (rel === "design.json") {
|
|
3989
4171
|
queue("config", () => {
|
|
@@ -4091,15 +4273,15 @@ async function ensureServer(paths, options) {
|
|
|
4091
4273
|
if (stale)
|
|
4092
4274
|
print(dim(`Restarting the preview server: it runs foss-design ${live2.version}, this is ${packageVersion()}.`));
|
|
4093
4275
|
if (live2) await stopServer(paths);
|
|
4094
|
-
|
|
4095
|
-
const log =
|
|
4276
|
+
fs23.mkdirSync(paths.cache, { recursive: true });
|
|
4277
|
+
const log = fs23.openSync(logFile(paths), "w");
|
|
4096
4278
|
const args = [PKG.cli, "preview", "--foreground", "--root", paths.root];
|
|
4097
4279
|
if (options.port) args.push("--port", String(options.port));
|
|
4098
4280
|
const child = spawn(process.execPath, args, { detached: true, stdio: ["ignore", log, log], cwd: paths.root });
|
|
4099
4281
|
child.unref();
|
|
4100
4282
|
const state = await waitForServer(paths, 4e4);
|
|
4101
4283
|
if (!state) {
|
|
4102
|
-
const tail =
|
|
4284
|
+
const tail = fs23.existsSync(logFile(paths)) ? fs23.readFileSync(logFile(paths), "utf8").split("\n").slice(-25) : [];
|
|
4103
4285
|
throw new CliError(`The preview server did not start.
|
|
4104
4286
|
${tail.join("\n")}`);
|
|
4105
4287
|
}
|
|
@@ -4160,7 +4342,7 @@ async function renderFrames(jobs) {
|
|
|
4160
4342
|
await page.close().catch(() => {
|
|
4161
4343
|
});
|
|
4162
4344
|
}
|
|
4163
|
-
return { key: job.key, url: job.url, ready: report.ready, errors: report.errors, links };
|
|
4345
|
+
return { key: job.key, url: job.url, ready: report.ready, held: report.held, errors: report.errors, links };
|
|
4164
4346
|
});
|
|
4165
4347
|
} finally {
|
|
4166
4348
|
await browser.close();
|
|
@@ -4203,7 +4385,8 @@ async function runCheck(paths, canvases, options) {
|
|
|
4203
4385
|
for (const frame of frames) {
|
|
4204
4386
|
const ok = frame.ready && !frame.errors.length;
|
|
4205
4387
|
print(`${ok ? green("\u2713") : red("\u2717")} ${frame.key} ${dim(frame.url)}`);
|
|
4206
|
-
if (
|
|
4388
|
+
if (frame.held) print(` ${red(`holdReady() was not released within 20s (${frame.held} still held)`)}`);
|
|
4389
|
+
else if (!frame.ready) print(` ${red("did not finish rendering (no ready signal within 20s)")}`);
|
|
4207
4390
|
for (const error of frame.errors) print(` ${red(error.split("\n")[0])}`);
|
|
4208
4391
|
}
|
|
4209
4392
|
}
|
|
@@ -4277,9 +4460,9 @@ function printLinkIssues(issues, project) {
|
|
|
4277
4460
|
}
|
|
4278
4461
|
|
|
4279
4462
|
// src/cli/commands/cloud.ts
|
|
4280
|
-
import
|
|
4463
|
+
import fs24 from "fs";
|
|
4281
4464
|
import os3 from "os";
|
|
4282
|
-
import
|
|
4465
|
+
import path22 from "path";
|
|
4283
4466
|
var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
4284
4467
|
async function runLogin() {
|
|
4285
4468
|
const host = cloudHost();
|
|
@@ -4380,7 +4563,7 @@ async function runLink(paths, wanted, options) {
|
|
|
4380
4563
|
print(`${dim("Already linked to")} ${project.name} ${dim(`(${project.id}, ${project.role})`)}`);
|
|
4381
4564
|
} else {
|
|
4382
4565
|
writeLink(paths, { host, project: project.id, units: {}, conflicts: {} });
|
|
4383
|
-
|
|
4566
|
+
fs24.rmSync(path22.join(cloudCache(paths), "incoming"), { recursive: true, force: true });
|
|
4384
4567
|
const replaced = current ? dim(` (replacing the link to ${current.project})`) : "";
|
|
4385
4568
|
print(`${green("linked")} .design \u2192 ${bold(project.name)} ${dim(`(${project.id}, ${project.role})`)}${replaced}`);
|
|
4386
4569
|
}
|
|
@@ -4405,8 +4588,8 @@ async function runLink(paths, wanted, options) {
|
|
|
4405
4588
|
}
|
|
4406
4589
|
|
|
4407
4590
|
// src/cli/commands/scaffold.ts
|
|
4408
|
-
import
|
|
4409
|
-
import
|
|
4591
|
+
import fs25 from "fs";
|
|
4592
|
+
import path23 from "path";
|
|
4410
4593
|
|
|
4411
4594
|
// src/cli/templates.ts
|
|
4412
4595
|
var TOKENS_CSS = `/*
|
|
@@ -4617,40 +4800,40 @@ function mainScreen(canvasId) {
|
|
|
4617
4800
|
|
|
4618
4801
|
// src/cli/commands/scaffold.ts
|
|
4619
4802
|
function write(file, content, root) {
|
|
4620
|
-
if (
|
|
4621
|
-
print(` ${dim("exists ")} ${
|
|
4803
|
+
if (fs25.existsSync(file)) {
|
|
4804
|
+
print(` ${dim("exists ")} ${path23.relative(root, file)}`);
|
|
4622
4805
|
return false;
|
|
4623
4806
|
}
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
print(` ${green("created")} ${
|
|
4807
|
+
fs25.mkdirSync(path23.dirname(file), { recursive: true });
|
|
4808
|
+
fs25.writeFileSync(file, content);
|
|
4809
|
+
print(` ${green("created")} ${path23.relative(root, file)}`);
|
|
4627
4810
|
return true;
|
|
4628
4811
|
}
|
|
4629
4812
|
function ensureGitignored(root) {
|
|
4630
|
-
const file =
|
|
4631
|
-
const current =
|
|
4813
|
+
const file = path23.join(root, ".gitignore");
|
|
4814
|
+
const current = fs25.existsSync(file) ? fs25.readFileSync(file, "utf8") : "";
|
|
4632
4815
|
const covered = current.split(/\r?\n/).map((line) => line.trim()).some((line) => [".design", ".design/", "/.design", "/.design/", ".design/*"].includes(line));
|
|
4633
4816
|
if (covered) return "present";
|
|
4634
4817
|
const prefix = current && !current.endsWith("\n") ? "\n" : "";
|
|
4635
|
-
|
|
4818
|
+
fs25.writeFileSync(file, `${current}${prefix}${current ? "\n" : ""}# Design canvases (foss-design)
|
|
4636
4819
|
.design/
|
|
4637
4820
|
`);
|
|
4638
4821
|
return "added";
|
|
4639
4822
|
}
|
|
4640
4823
|
function projectName(root) {
|
|
4641
4824
|
try {
|
|
4642
|
-
const manifest = JSON.parse(
|
|
4825
|
+
const manifest = JSON.parse(fs25.readFileSync(path23.join(root, "package.json"), "utf8"));
|
|
4643
4826
|
if (typeof manifest.name === "string" && manifest.name) return manifest.name;
|
|
4644
4827
|
} catch {
|
|
4645
4828
|
}
|
|
4646
|
-
return
|
|
4829
|
+
return path23.basename(root);
|
|
4647
4830
|
}
|
|
4648
4831
|
function runInit(root, options) {
|
|
4649
4832
|
const paths = designPaths(root);
|
|
4650
4833
|
print(`Setting up ${DESIGN_DIR} in ${root}`);
|
|
4651
4834
|
write(paths.config, `${JSON.stringify({ name: options.name ?? projectName(root) }, null, 2)}
|
|
4652
4835
|
`, root);
|
|
4653
|
-
|
|
4836
|
+
fs25.mkdirSync(paths.canvases, { recursive: true });
|
|
4654
4837
|
if (options.gitignore) {
|
|
4655
4838
|
const result = ensureGitignored(root);
|
|
4656
4839
|
print(` ${result === "added" ? green("ignored") : dim("ignored")} .design/ in .gitignore`);
|
|
@@ -4662,44 +4845,44 @@ function runSystemInit(root, options) {
|
|
|
4662
4845
|
const paths = designPaths(root);
|
|
4663
4846
|
let base = projectName(root);
|
|
4664
4847
|
try {
|
|
4665
|
-
base = JSON.parse(
|
|
4848
|
+
base = JSON.parse(fs25.readFileSync(paths.config, "utf8")).name || base;
|
|
4666
4849
|
} catch {
|
|
4667
4850
|
}
|
|
4668
4851
|
const name = options.name ?? `${base} design system`;
|
|
4669
|
-
print(`Scaffolding the design system in ${
|
|
4670
|
-
write(
|
|
4852
|
+
print(`Scaffolding the design system in ${path23.relative(root, paths.system)}`);
|
|
4853
|
+
write(path23.join(paths.system, "system.json"), `${JSON.stringify({ name }, null, 2)}
|
|
4671
4854
|
`, root);
|
|
4672
|
-
write(
|
|
4855
|
+
write(path23.join(paths.system, "tokens.css"), TOKENS_CSS, root);
|
|
4673
4856
|
if (!options.empty) {
|
|
4674
|
-
write(
|
|
4675
|
-
write(
|
|
4676
|
-
write(
|
|
4857
|
+
write(path23.join(paths.system, "guidelines", "01-overview.md"), OVERVIEW_MD, root);
|
|
4858
|
+
write(path23.join(paths.system, "components", "button.tsx"), BUTTON_TSX, root);
|
|
4859
|
+
write(path23.join(paths.system, "specimens", "button.tsx"), BUTTON_SPECIMEN_TSX, root);
|
|
4677
4860
|
}
|
|
4678
4861
|
}
|
|
4679
4862
|
function runNew(root, id2, options) {
|
|
4680
4863
|
if (!ID_PATTERN.test(id2)) throw new CliError(`"${id2}" is not a valid canvas id: use letters, digits, "-" and "_"`);
|
|
4681
4864
|
const paths = designPaths(root);
|
|
4682
|
-
const dir =
|
|
4683
|
-
if (
|
|
4865
|
+
const dir = path23.join(paths.canvases, id2);
|
|
4866
|
+
if (fs25.existsSync(path23.join(dir, "canvas.json"))) throw new CliError(`Canvas "${id2}" already exists in ${dir}`);
|
|
4684
4867
|
print(`Creating canvas "${id2}"`);
|
|
4685
4868
|
const title = options.title ?? titleize(id2);
|
|
4686
4869
|
if (options.empty) {
|
|
4687
4870
|
write(
|
|
4688
|
-
|
|
4871
|
+
path23.join(dir, "canvas.json"),
|
|
4689
4872
|
`${JSON.stringify({ title, pages: [{ id: "main", title: "Main", sections: [] }] }, null, 2)}
|
|
4690
4873
|
`,
|
|
4691
4874
|
root
|
|
4692
4875
|
);
|
|
4693
|
-
|
|
4876
|
+
fs25.mkdirSync(path23.join(dir, "screens"), { recursive: true });
|
|
4694
4877
|
} else {
|
|
4695
|
-
write(
|
|
4696
|
-
write(
|
|
4878
|
+
write(path23.join(dir, "canvas.json"), canvasJson(title), root);
|
|
4879
|
+
write(path23.join(dir, "screens", "Main.tsx"), mainScreen(id2), root);
|
|
4697
4880
|
}
|
|
4698
4881
|
}
|
|
4699
4882
|
|
|
4700
4883
|
// src/cli/commands/shot.ts
|
|
4701
|
-
import
|
|
4702
|
-
import
|
|
4884
|
+
import fs26 from "fs";
|
|
4885
|
+
import path24 from "path";
|
|
4703
4886
|
var OVERVIEW_TIMEOUT = 9e4;
|
|
4704
4887
|
async function runShot(paths, target, options) {
|
|
4705
4888
|
const [canvasId = "", screenId] = target.split("/");
|
|
@@ -4715,15 +4898,15 @@ async function runShot(paths, target, options) {
|
|
|
4715
4898
|
throw new CliError(`No page "${options.page}" in "${canvasId}". Pages: ${doc.pages.map((p) => p.id).join(", ")}`);
|
|
4716
4899
|
}
|
|
4717
4900
|
const theme2 = options.theme ?? doc.theme ?? "light";
|
|
4718
|
-
const outBase =
|
|
4719
|
-
const outDir =
|
|
4720
|
-
|
|
4901
|
+
const outBase = path24.resolve(options.out ?? paths.shots);
|
|
4902
|
+
const outDir = path24.join(outBase, canvasId);
|
|
4903
|
+
fs26.mkdirSync(outDir, { recursive: true });
|
|
4721
4904
|
const base = await serverBase(paths);
|
|
4722
4905
|
const browser = await launchChrome();
|
|
4723
4906
|
let failed = false;
|
|
4724
4907
|
try {
|
|
4725
4908
|
if (options.overview) {
|
|
4726
|
-
if (!
|
|
4909
|
+
if (!fs26.existsSync(PKG.viewer)) throw new CliError("The viewer is not built, so there is no canvas to shoot.");
|
|
4727
4910
|
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);
|
|
4728
4911
|
for (const pageId of pages) {
|
|
4729
4912
|
const page = await browser.newPage({ viewport: { width: 1920, height: 1200 } });
|
|
@@ -4739,7 +4922,7 @@ async function runShot(paths, target, options) {
|
|
|
4739
4922
|
warn(`page "${pageId}": not every frame reported ready within ${OVERVIEW_TIMEOUT / 1e3}s; shooting anyway`);
|
|
4740
4923
|
}
|
|
4741
4924
|
await page.waitForTimeout(400);
|
|
4742
|
-
const file =
|
|
4925
|
+
const file = path24.join(outDir, `${pageId}.overview.png`);
|
|
4743
4926
|
await page.screenshot({ path: file });
|
|
4744
4927
|
await page.close();
|
|
4745
4928
|
print(file);
|
|
@@ -4768,7 +4951,7 @@ async function shootFrames(browser, jobs, outDir) {
|
|
|
4768
4951
|
let failed = false;
|
|
4769
4952
|
const results = await mapLimit(jobs, 4, async (job) => {
|
|
4770
4953
|
const page = await browser.newPage({ viewport: { width: job.width, height: job.height } });
|
|
4771
|
-
const file =
|
|
4954
|
+
const file = path24.join(outDir, `${job.id}.${job.theme}.png`);
|
|
4772
4955
|
try {
|
|
4773
4956
|
const report = await openFrame(page, job.url, { waitForReady: job.waitForReady });
|
|
4774
4957
|
await page.screenshot({ path: file, fullPage: job.fullPage });
|
|
@@ -4800,8 +4983,8 @@ async function shootSystem(paths, project, id2, options) {
|
|
|
4800
4983
|
if (id2 && !all.some((job) => job.id === id2)) {
|
|
4801
4984
|
throw new CliError(`No specimen "${id2}" in the design system. Specimens: ${all.map((job) => job.id).join(", ")}`);
|
|
4802
4985
|
}
|
|
4803
|
-
const outDir =
|
|
4804
|
-
|
|
4986
|
+
const outDir = path24.join(path24.resolve(options.out ?? paths.shots), SYSTEM_CANVAS);
|
|
4987
|
+
fs26.mkdirSync(outDir, { recursive: true });
|
|
4805
4988
|
const base = await serverBase(paths);
|
|
4806
4989
|
const jobs = systemFrames(system.doc, base, theme2).filter((job) => !id2 || job.id === id2);
|
|
4807
4990
|
const browser = await launchChrome();
|
|
@@ -4813,7 +4996,7 @@ async function shootSystem(paths, project, id2, options) {
|
|
|
4813
4996
|
}
|
|
4814
4997
|
|
|
4815
4998
|
// src/cli/commands/cloud-status.ts
|
|
4816
|
-
import
|
|
4999
|
+
import path25 from "path";
|
|
4817
5000
|
var STATE_TEXT = {
|
|
4818
5001
|
in_sync: green,
|
|
4819
5002
|
local_changes: yellow,
|
|
@@ -4836,7 +5019,7 @@ function detail(paths, status) {
|
|
|
4836
5019
|
if (status.state === "remote_ahead" && !status.local && !status.baseRev) parts.push("not pulled yet");
|
|
4837
5020
|
if (status.state === "remote_ahead" && status.changed.length && !status.deletedRemotely)
|
|
4838
5021
|
parts.push(`${plural(status.changed.length, "file")} changed here; the cloud only rebuilt it`);
|
|
4839
|
-
if (status.conflictRev !== void 0) parts.push(`merge ${
|
|
5022
|
+
if (status.conflictRev !== void 0) parts.push(`merge ${path25.relative(paths.root, incomingDir(paths, status.key))}`);
|
|
4840
5023
|
return parts.join(" \xB7 ");
|
|
4841
5024
|
}
|
|
4842
5025
|
async function printCloudStatus(paths, label) {
|
|
@@ -5010,7 +5193,7 @@ ${bold("Environment")}
|
|
|
5010
5193
|
FOSS_DESIGN_CLOUD Cloud to sign in and link to (default https://app.fossdesign.dev)
|
|
5011
5194
|
`;
|
|
5012
5195
|
function projectPaths(rootOption, requireDesign = true) {
|
|
5013
|
-
const found = rootOption ?
|
|
5196
|
+
const found = rootOption ? path26.resolve(rootOption) : findProjectRoot();
|
|
5014
5197
|
if (!found) {
|
|
5015
5198
|
if (!requireDesign) return designPaths(process.cwd());
|
|
5016
5199
|
throw new CliError("No .design folder here or above. Run `design init` in the project root first.");
|
|
@@ -5056,7 +5239,7 @@ async function main(argv) {
|
|
|
5056
5239
|
if (values.theme && !theme2) throw new CliError("--theme is light or dark");
|
|
5057
5240
|
switch (command) {
|
|
5058
5241
|
case "init":
|
|
5059
|
-
return runInit(values.root ?
|
|
5242
|
+
return runInit(values.root ? path26.resolve(values.root) : process.cwd(), {
|
|
5060
5243
|
name: values.name,
|
|
5061
5244
|
gitignore: !values["no-gitignore"]
|
|
5062
5245
|
});
|